#!/bin/sh # Copyright (c) 2012 dak180 # See http://opensource.org/licenses/bsd-license.php for licence terms # # autorevision - extracts metadata about the head version from your repository. # Usage message. arUsage() { cat > "/dev/stderr" << EOF usage: ./autorevision {-t output-type | -s symbol} [-o cache-file [-f] ] [-V] Options include: -t output-type = specify output type -s symbol = specify symbol output -o cache-file = specify cache file location -f = force the use of cache data -V = emit version and exit -? = help message The folowing are valid output types: h = Header for use with c/c++ xcode = Header useful for populating info.plist files sh = Bash sytax py = Python file pl = Perl file lua = Lua file php = PHP file ini = INI file js = javascript file java = Java file javaprop = Java properties file tex = (La)TeX file m4 = m4 file The following are valid symbols: VCS_TYPE VCS_BASENAME VCS_NUM VCS_DATE VCS_BRANCH VCS_TAG VCS_TICK VCS_FULL_HASH VCS_SHORT_HASH VCS_WC_MODIFIED EOF exit 1 } # Config ARVERSION="1.4" TARGETFILE="/dev/stdout" while getopts ":t:o:s:Vf" OPTION; do case "${OPTION}" in t) AFILETYPE="${OPTARG}" ;; o) CACHEFILE="${OPTARG}" ;; f) CACHEFORCE="1" ;; s) VAROUT="${OPTARG}" ;; V) echo "autorevision ${ARVERSION}" exit 0 ;; ?) # If an unknown flag is used (or -?): arUsage ;; esac done if [ ! -z "${VAROUT}" ] && [ ! -z "${AFILETYPE}" ]; then # If both -s and -t are specified: echo "error: Improper argument combination." 1>&2 exit 1 elif [ -z "${VAROUT}" ] && [ -z "${AFILETYPE}" ]; then # If neither -s or -t are specified: arUsage elif [ -z "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then # If -f is specified without -o: arUsage fi # Functions to extract data from different repo types. # For git repos gitRepo() { cd "$(git rev-parse --show-toplevel)" VCS_TYPE="git" VCS_BASENAME="$(basename "${PWD}")" # Is the working copy clean? test -z "$(git status -uno --porcelain)" VCS_WC_MODIFIED="${?}" # Enumeration of changesets VCS_NUM="$(git rev-list --count HEAD 2>/dev/null)" if [ -z "${VCS_NUM}" ]; then echo "warning: Counting the number of revisions may be slower due to an outdated git version less than 1.7.2.3. If something breaks, please update it." 1>&2 VCS_NUM="$(git rev-list HEAD | wc -l)" fi # The full revision hash VCS_FULL_HASH="$(git rev-parse HEAD)" # The short hash VCS_SHORT_HASH="$(echo "${VCS_FULL_HASH}" | cut -b 1-7)" # Current branch VCS_BRANCH="$(git rev-parse --symbolic-full-name --verify "$(git name-rev --name-only --no-undefined HEAD 2>/dev/null)" 2>/dev/null | sed -e 's:refs/heads/::' | sed -e 's:refs/::')" # Cache the description DESCRIPTION="$(git describe --long --tags 2>/dev/null)" # Current or last tag ancestor (empty if no tags) VCS_TAG="$(echo "${DESCRIPTION}" | sed -e "s:-g${VCS_SHORT_HASH}\$::" | sed -e 's:-[0-9]*$::')" # Distance to last tag or an alias of VCS_NUM if there is no tag if [ ! -z "${DESCRIPTION}" ]; then VCS_TICK="$(echo "${DESCRIPTION}" | sed -e "s:${VCS_TAG}-::" -e "s:-g${VCS_SHORT_HASH}::")" else VCS_TICK="${VCS_NUM}" fi # Date of the current commit VCS_DATE="$(git log -1 --pretty=format:%ci | sed -e 's: :T:' | sed -e 's: ::')" } # For hg repos hgRepo() { cd "$(hg root)" VCS_TYPE="hg" VCS_BASENAME="$(basename "${PWD}")" # Is the working copy clean? hg sum | grep -q 'commit: (clean)' VCS_WC_MODIFIED="${?}" # Enumeration of changesets VCS_NUM="$(hg id -n | tr -d '+')" # The full revision hash VCS_FULL_HASH="$(hg log -r "${VCS_NUM}" -l 1 --template '{node}\n')" # The short hash VCS_SHORT_HASH="$(hg id -i | tr -d '+')" # Current bookmark (bookmarks are roughly equivalent to git's branches) # or branch if no bookmark VCS_BRANCH="$(hg id -B | cut -d ' ' -f 1)" # Fall back to the branch if there are no bookmarks if [ -z "${VCS_BRANCH}" ]; then VCS_BRANCH="$(hg id -b)" fi # Current or last tag ancestor (excluding auto tags, empty if no tags) VCS_TAG="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttag}\n' 2>/dev/null | sed -e 's:qtip::' -e 's:tip::' -e 's:qbase::' -e 's:qparent::' -e "s:$(hg --config 'extensions.color=' --color never qtop 2>/dev/null)::" | cut -d ' ' -f 1)" # Distance to last tag or an alias of VCS_NUM if there is no tag if [ ! -z "${VCS_TAG}" ]; then VCS_TICK="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttagdistance}\n' 2>/dev/null)" else VCS_TICK="${VCS_NUM}" fi # Date of the current commit VCS_DATE="$(hg log -r "${VCS_NUM}" -l 1 --template '{date|isodatesec}\n' 2>/dev/null | sed -e 's: :T:' | sed -e 's: ::')" } # For bzr repos bzrRepo() { cd "$(bzr root)" VCS_TYPE="bzr" VCS_BASENAME="$(basename "${PWD}")" # Is the working copy clean? bzr version-info --custom --template='{clean}\n' | grep -q '1' VCS_WC_MODIFIED="${?}" # Enumeration of changesets VCS_NUM="$(bzr revno)" # The full revision hash VCS_FULL_HASH="$(bzr version-info --custom --template='{revision_id}\n')" # The short hash VCS_SHORT_HASH="${VCS_NUM}" # Nick of the current branch VCS_BRANCH="$(bzr nick)" # Current or last tag ancestor (excluding auto tags, empty if no tags) VCS_TAG="$(bzr tags --sort=time | sed '/?$/d' | tail -n1 | cut -d ' ' -f1)" # Distance to last tag or an alias of VCS_NUM if there is no tag if [ ! -z "${VCS_TAG}" ]; then VCS_TICK="$(bzr log --line -r "tag:${VCS_TAG}.." | tail -n +2 | wc -l | sed -e 's:^ *::')" else VCS_TICK="${VCS_NUM}" fi # Date of the current commit VCS_DATE="$(bzr version-info --custom --template='{date}\n' | sed -e 's: :T:' | sed -e 's: ::')" } # For svn repos svnRepo() { VCS_TYPE="svn" case "${PWD}" in /*trunk*|/*branches*|/*tags*) fn="${PWD}" while [ "$(basename "${fn}")" != 'trunk' ] && [ "$(basename "${fn}")" != 'branches' ] && [ "$(basename "${fn}")" != 'tags' ] && [ "$(basename "${fn}")" != '/' ]; do fn="$(dirname "${fn}")" done fn="$(dirname "${fn}")" if [ "${fn}" = '/' ]; then VCS_BASENAME="$(basename "${PWD}")" else VCS_BASENAME="$(basename "${fn}")" fi ;; *) VCS_BASENAME="$(basename "${PWD}")" ;; esac # Cache svnversion output SVNVERSION="$(svnversion)" # Is the working copy clean? echo "${SVNVERSION}" | grep -q "M" case "${?}" in 0) VCS_WC_MODIFIED="1";; 1) VCS_WC_MODIFIED="0";; esac # Enumeration of changesets VCS_NUM="$(echo "${SVNVERSION}" | cut -d : -f 1 | sed -e 's:M::' -e 's:S::' -e 's:P::')" # The full revision hash VCS_FULL_HASH="${SVNVERSION}" # The short hash VCS_SHORT_HASH="${VCS_NUM}" # Current branch case "${PWD}" in /*trunk*|/*branches*|/*tags*) lastbase="" fn="${PWD}" while : do base="$(basename "${fn}")" if [ "${base}" = 'trunk' ]; then VCS_BRANCH='trunk' break elif [ "${base}" = 'branches' ] || [ "${base}" = 'tags' ]; then VCS_BRANCH="${lastbase}" break elif [ "${base}" = '/' ]; then VCS_BRANCH="" break fi lastbase="${base}" fn="$(dirname "${fn}")" done ;; *) VCS_BRANCH="" ;; esac # Current or last tag ancestor (empty if no tags). But "current tag" # can't be extracted reliably because Subversion doesn't have tags the # way other VCSes do. VCS_TAG="" VCS_TICK="" # Date of the current commit VCS_DATE="$(svn info | sed -n -e 's:Last Changed Date\: ::p' | sed 's: (.*)::' | sed -e 's: :T:' | sed -e 's: ::')" } # Functions to output data in different formats. # For header output hOutput() { cat > "${TARGETFILE}" << EOF /* Generated by autorevision - do not hand-hack! */ #ifndef AUTOREVISION_H #define AUTOREVISION_H #define VCS_TYPE "${VCS_TYPE}" #define VCS_BASENAME "${VCS_BASENAME}" #define VCS_NUM ${VCS_NUM} #define VCS_DATE "${VCS_DATE}" #define VCS_BRANCH "${VCS_BRANCH}" #define VCS_TAG "${VCS_TAG}" #define VCS_TICK ${VCS_TICK} #define VCS_FULL_HASH "${VCS_FULL_HASH}" #define VCS_SHORT_HASH "${VCS_SHORT_HASH}" #define VCS_WC_MODIFIED ${VCS_WC_MODIFIED} #endif /* end */ EOF } # A header output for use with xcode to populate info.plist strings xcodeOutput() { cat > "${TARGETFILE}" << EOF /* Generated by autorevision - do not hand-hack! */ #ifndef AUTOREVISION_H #define AUTOREVISION_H #define VCS_TYPE ${VCS_TYPE} #define VCS_BASENAME ${VCS_BASENAME} #define VCS_NUM ${VCS_NUM} #define VCS_DATE ${VCS_DATE} #define VCS_BRANCH ${VCS_BRANCH} #define VCS_TAG ${VCS_TAG} #define VCS_TICK ${VCS_TICK} #define VCS_FULL_HASH ${VCS_FULL_HASH} #define VCS_SHORT_HASH ${VCS_SHORT_HASH} #define VCS_WC_MODIFIED ${VCS_WC_MODIFIED} #endif /* end */ EOF } # For bash output shOutput() { cat > "${TARGETFILE}" << EOF # Generated by autorevision - do not hand-hack! VCS_TYPE="${VCS_TYPE}" VCS_BASENAME="${VCS_BASENAME}" VCS_NUM=${VCS_NUM} VCS_DATE="${VCS_DATE}" VCS_BRANCH="${VCS_BRANCH}" VCS_TAG="${VCS_TAG}" VCS_TICK=${VCS_TICK} VCS_FULL_HASH="${VCS_FULL_HASH}" VCS_SHORT_HASH="${VCS_SHORT_HASH}" VCS_WC_MODIFIED=${VCS_WC_MODIFIED} # end EOF } # For Python output pyOutput() { case "${VCS_WC_MODIFIED}" in 0) VCS_WC_MODIFIED="False" ;; 1) VCS_WC_MODIFIED="True" ;; esac cat > "${TARGETFILE}" << EOF # Generated by autorevision - do not hand-hack! VCS_TYPE = "${VCS_TYPE}" VCS_BASENAME = "${VCS_BASENAME}" VCS_NUM = ${VCS_NUM} VCS_DATE = "${VCS_DATE}" VCS_BRANCH = "${VCS_BRANCH}" VCS_TAG = "${VCS_TAG}" VCS_TICK = ${VCS_TICK} VCS_FULL_HASH = "${VCS_FULL_HASH}" VCS_SHORT_HASH = "${VCS_SHORT_HASH}" VCS_WC_MODIFIED = ${VCS_WC_MODIFIED} # end EOF } # For Perl output plOutput() { cat << EOF # Generated by autorevision - do not hand-hack! \$VCS_TYPE = "${VCS_TYPE}"; \$VCS_BASENAME = "${VCS_BASENAME}" \$VCS_NUM = ${VCS_NUM}; \$VCS_DATE = "${VCS_DATE}"; \$VCS_BRANCH = "${VCS_BRANCH}"; \$VCS_TAG = "${VCS_TAG}"; \$VCS_TICK = ${VCS_TICK}; \$VCS_FULL_HASH = "${VCS_FULL_HASH}"; \$VCS_SHORT_HASH = "${VCS_SHORT_HASH}"; \$VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; # end EOF } # For lua output luaOutput() { case "${VCS_WC_MODIFIED}" in 0) VCS_WC_MODIFIED="false" ;; 1) VCS_WC_MODIFIED="true" ;; esac cat > "${TARGETFILE}" << EOF -- Generated by autorevision - do not hand-hack! VCS_TYPE = "${VCS_TYPE}" VCS_BASENAME = "${VCS_BASENAME}" VCS_NUM = ${VCS_NUM} VCS_DATE = "${VCS_DATE}" VCS_BRANCH = "${VCS_BRANCH}" VCS_TAG = "${VCS_TAG}" VCS_TICK = ${VCS_TICK} VCS_FULL_HASH = "${VCS_FULL_HASH}" VCS_SHORT_HASH = "${VCS_SHORT_HASH}" VCS_WC_MODIFIED = ${VCS_WC_MODIFIED} -- end EOF } # For php output phpOutput() { case "${VCS_WC_MODIFIED}" in 0) VCS_WC_MODIFIED="false" ;; 1) VCS_WC_MODIFIED="true" ;; esac cat > "${TARGETFILE}" << EOF EOF } # For ini output iniOutput() { case "${VCS_WC_MODIFIED}" in 0) VCS_WC_MODIFIED="false" ;; 1) VCS_WC_MODIFIED="true" ;; esac cat > "${TARGETFILE}" << EOF ; Generated by autorevision - do not hand-hack! [VCS] VCS_TYPE = "${VCS_TYPE}" VCS_BASENAME = "${VCS_BASENAME}" VCS_NUM = ${VCS_NUM} VCS_DATE = "${VCS_DATE}" VCS_BRANCH = "${VCS_BRANCH}" VCS_TAG = "${VCS_TAG}" VCS_TICK = ${VCS_TICK} VCS_FULL_HASH = "${VCS_FULL_HASH}" VCS_SHORT_HASH = "${VCS_SHORT_HASH}" VCS_WC_MODIFIED = ${VCS_WC_MODIFIED} ; end EOF } # For javascript output jsOutput() { case "${VCS_WC_MODIFIED}" in 1) VCS_WC_MODIFIED="true" ;; 0) VCS_WC_MODIFIED="false" ;; esac cat > "${TARGETFILE}" << EOF /** Generated by autorevision - do not hand-hack! */ var autorevision = { VCS_TYPE: "${VCS_TYPE}", VCS_BASENAME: "${VCS_BASENAME}", VCS_NUM: ${VCS_NUM}, VCS_DATE: "${VCS_DATE}", VCS_BRANCH: "${VCS_BRANCH}", VCS_TAG: "${VCS_TAG}", VCS_TICK: ${VCS_TICK}, VCS_FULL_HASH: "${VCS_FULL_HASH}", VCS_SHORT_HASH: "${VCS_SHORT_HASH}", VCS_WC_MODIFIED: ${VCS_WC_MODIFIED} }; /** Node.js compatibility */ if (typeof module !== 'undefined') { module.exports = autorevision; } /** end */ EOF } # For Java output javaOutput() { case "${VCS_WC_MODIFIED}" in 1) VCS_WC_MODIFIED="true" ;; 0) VCS_WC_MODIFIED="false" ;; esac cat > "${TARGETFILE}" << EOF /* Generated by autorevision - do not hand-hack! */ import java.util.Date; public class autorevision { public static final String VCS_TYPE = "${VCS_TYPE}"; public static final String VCS_BASENAME = "${VCS_BASENAME}"; public static final long VCS_NUM = ${VCS_NUM}; public static final Date VCS_DATE = new Date($(date -d ${VCS_DATE} +%s)); public static final String VCS_BRANCH = "${VCS_BRANCH}"; public static final String VCS_TAG = "${VCS_TAG}"; public static final long VCS_TICK = ${VCS_TICK}; public static final String VCS_FULL_HASH = "${VCS_FULL_HASH}"; public static final String VCS_SHORT_HASH = "${VCS_SHORT_HASH}"; public static final boolean VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; } EOF } # For Java properties output javapropOutput() { case "${VCS_WC_MODIFIED}" in 1) VCS_WC_MODIFIED="true" ;; 0) VCS_WC_MODIFIED="false" ;; esac cat > "${TARGETFILE}" << EOF # Generated by autorevision - do not hand-hack! VCS_TYPE=${VCS_TYPE} VCS_BASENAME=${VCS_BASENAME} VCS_NUM=${VCS_NUM} VCS_DATE=${VCS_DATE} VCS_BRANCH=${VCS_BRANCH} VCS_TAG=${VCS_TAG} VCS_TICK=${VCS_TICK} VCS_FULL_HASH=${VCS_FULL_HASH} VCS_SHORT_HASH=${VCS_SHORT_HASH} VCS_WC_MODIFIED=${VCS_WC_MODIFIED} EOF } # For m4 output m4Output() { cat > "${TARGETFILE}" << EOF define(\`VCS_TYPE', \`${VCS_TYPE}')dnl define(\`VCS_BASENAME', \`${VCS_BASENAME}')dnl define(\`VCS_NUM', \`${VCS_NUM}')dnl define(\`VCS_DATE', \`${VCS_DATE}')dnl define(\`VCS_BRANCH', \`${VCS_BRANCH}')dnl define(\`VCS_TAG', \`${VCS_TAG}')dnl define(\`VCS_TICK', \`${VCS_TICK}')dnl define(\`VCS_FULLHASH', \`${VCS_FULL_HASH}')dnl define(\`VCS_SHIRTHASH', \`${VCS_SHORT_HASH}')dnl define(\`VCS_WC_MODIFIED', \`${VCS_WC_MODIFIED}')dnl EOF } # For (La)TeX output texOutput() { case "${VCS_WC_MODIFIED}" in 0) VCS_WC_MODIFIED="false" ;; 1) VCS_WC_MODIFIED="true" ;; esac cat > "${TARGETFILE}" << EOF % Generated by autorevision - do not hand-hack! \def \vcsType {${VCS_TYPE}} \def \vcsBasename {${VCS_BASENAME}} \def \vcsNum {${VCS_NUM}} \def \vcsDate {${VCS_DATE}} \def \vcsBranch {${VCS_BRANCH}} \def \vcsTag {${VCS_TAG}} \def \vcsTick {${VCS_TICK}} \def \vcsFullHash {${VCS_FULL_HASH}} \def \vcsShortHash {${VCS_SHORT_HASH}} \def \vcsWCModified {${VCS_WC_MODIFIED}} \endinput EOF } # Detect and collect repo data. if [ -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then # When requested only read from the cache to populate our symbols. source "${CACHEFILE}" elif [ ! -z "$(git rev-parse HEAD 2>/dev/null)" ]; then gitRepo elif [ ! -z "$(hg root 2>/dev/null)" ]; then hgRepo elif [ ! -z "$(bzr root 2>/dev/null)" ]; then bzrRepo elif [ ! -z "$(svn info 2>/dev/null)" ]; then svnRepo elif [ -f "${CACHEFILE}" ]; then # We are not in a repo; try to use a previously generated cache to populate our symbols. source "${CACHEFILE}" else echo "error: No repo or cache detected." 1>&2 exit 1 fi # -s output is handled here. if [ ! -z "${VAROUT}" ]; then if [ "${VAROUT}" = "VCS_TYPE" ]; then echo "${VCS_TYPE}" elif [ "${VAROUT}" = "VCS_BASENAME" ]; then echo "${VCS_BASENAME}" elif [ "${VAROUT}" = "VCS_NUM" ]; then echo "${VCS_NUM}" elif [ "${VAROUT}" = "VCS_DATE" ]; then echo "${VCS_DATE}" elif [ "${VAROUT}" = "VCS_BRANCH" ]; then echo "${VCS_BRANCH}" elif [ "${VAROUT}" = "VCS_TAG" ]; then echo "${VCS_TAG}" elif [ "${VAROUT}" = "VCS_TICK" ]; then echo "${VCS_TICK}" elif [ "${VAROUT}" = "VCS_FULL_HASH" ]; then echo "${VCS_FULL_HASH}" elif [ "${VAROUT}" = "VCS_SHORT_HASH" ]; then echo "${VCS_SHORT_HASH}" elif [ "${VAROUT}" = "VCS_WC_MODIFIED" ]; then echo "${VCS_WC_MODIFIED}" fi fi # Detect requested output type and use it. if [ ! -z "${AFILETYPE}" ]; then if [ "${AFILETYPE}" = "h" ]; then hOutput elif [ "${AFILETYPE}" = "xcode" ]; then xcodeOutput elif [ "${AFILETYPE}" = "sh" ]; then shOutput elif [ "${AFILETYPE}" = "py" ] || [ "${AFILETYPE}" = "python" ]; then pyOutput elif [ "${AFILETYPE}" = "pl" ] || [ "${AFILETYPE}" = "perl" ]; then plOutput elif [ "${AFILETYPE}" = "lua" ]; then luaOutput elif [ "${AFILETYPE}" = "php" ]; then phpOutput elif [ "${AFILETYPE}" = "ini" ]; then iniOutput elif [ "${AFILETYPE}" = "js" ]; then jsOutput elif [ "${AFILETYPE}" = "java" ]; then javaOutput elif [ "${AFILETYPE}" = "javaprop" ]; then javapropOutput elif [ "${AFILETYPE}" = "tex" ]; then texOutput elif [ "${AFILETYPE}" = "m4" ]; then m4Output else echo "error: Not a valid output type." 1>&2 exit 1 fi fi # If requested, make a cache file. if [ ! -z "${CACHEFILE}" ] && [ ! "${CACHEFORCE}" = "1" ]; then TARGETFILE="${CACHEFILE}" shOutput fi