#!/bin/sh # Copyright (c) 2012 - 2016 dak180 and contributors. See # https://opensource.org/licenses/mit-license.php or the included # COPYING.md for licence terms. # # autorevision - extracts metadata about the head version from your # repository. # Usage message. arUsage() { cat >"/dev/stderr" <&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 elif [ ! -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then # If we are forced to use the cache but it does not exist. echo "error: Cache forced but no cache found." 1>&2 exit 1 fi # Make sure that the path we are given is one we can source # (dash, we are looking at you). if [ ! -z "${CACHEFILE}" ] && ! echo "${CACHEFILE}" | grep -q '^\.*/'; then CACHEFILE="./${CACHEFILE}" fi GENERATED_HEADER="Generated by autorevision - do not hand-hack!" # Functions to extract data from different repo types. # For git repos # shellcheck disable=SC2039,SC2164,SC2155 gitRepo() { local oldPath="${PWD}" cd "$(git rev-parse --show-toplevel)" VCS_TYPE="git" VCS_BASENAME="$(basename "${PWD}")" VCS_UUID="$(git rev-list --max-parents=0 --date-order --reverse HEAD 2>/dev/null | sed -n 1p)" if [ -z "${VCS_UUID}" ]; then VCS_UUID="$(git rev-list --topo-order HEAD | tail -n 1)" fi # Is the working copy clean? test -z "$(git status --untracked-files=normal --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 # This may be a git-svn remote. If so, report the Subversion revision. if [ -z "$(git config svn-remote.svn.url 2>/dev/null)" ]; then # 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)" else # The git-svn revision number VCS_FULL_HASH="$(git svn find-rev HEAD)" VCS_SHORT_HASH="${VCS_FULL_HASH}" fi # 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 local 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}\$::" -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="$(TZ=UTC git show -s --date=iso-strict-local --pretty=format:%ad | sed -e 's|+00:00|Z|')" if [ -z "${VCS_DATE}" ]; then echo "warning: Action stamps require git version 2.7+." 1>&2 VCS_DATE="$(git log -1 --pretty=format:%ci | sed -e 's: :T:' -e 's: ::' -e 's|+00:00|Z|')" local ASdis="1" fi # Action Stamp if [ -z "${ASdis}" ]; then VCS_ACTION_STAMP="${VCS_DATE}!$(git show -s --pretty=format:%cE)" else VCS_ACTION_STAMP="" fi cd "${oldPath}" } # For hg repos # shellcheck disable=SC2039,SC2164 hgRepo() { local oldPath="${PWD}" cd "$(hg root)" VCS_TYPE="hg" VCS_BASENAME="$(basename "${PWD}")" VCS_UUID="$(hg log -r "0" -l 1 --template '{node}\n')" # Is the working copy clean? test -z "$(hg status -duram)" 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=' --config 'extensions.mq=' --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:' -e 's: ::' -e 's|+00:00|Z|')" # Action Stamp VCS_ACTION_STAMP="$(TZ=UTC hg log -r "${VCS_NUM}" -l 1 --template '{date|localdate|rfc3339date}\n' 2>/dev/null | sed -e 's|+00:00|Z|')!$(hg log -r "${VCS_NUM}" -l 1 --template '{author|email}\n' 2>/dev/null)" cd "${oldPath}" } # For bzr repos # shellcheck disable=SC2039,SC2164 bzrRepo() { local oldPath="${PWD}" cd "$(bzr root)" VCS_TYPE="bzr" VCS_BASENAME="$(basename "${PWD}")" # Currently unimplemented because more investigation is needed. VCS_UUID="" # 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:' -e 's: ::')" # Action Stamp # Currently unimplemented because more investigation is needed. VCS_ACTION_STAMP="" cd "${oldPath}" } # For svn repos # shellcheck disable=SC2039,SC2164,SC2155 svnRepo() { local oldPath="${PWD}" VCS_TYPE="svn" case "${PWD}" in /*trunk* | /*branches* | /*tags*) local fn="${PWD}" while [ "$(basename "${fn}")" != 'trunk' ] && [ "$(basename "${fn}")" != 'branches' ] && [ "$(basename "${fn}")" != 'tags' ] && [ "$(basename "${fn}")" != '/' ]; do local fn="$(dirname "${fn}")" done local fn="$(dirname "${fn}")" if [ "${fn}" = '/' ]; then VCS_BASENAME="$(basename "${PWD}")" else VCS_BASENAME="$(basename "${fn}")" fi ;; *) VCS_BASENAME="$(basename "${PWD}")" ;; esac VCS_UUID="$(svn info --xml | sed -n -e 's:::' -e 's:::p')" # Cache svnversion output local SVNVERSION="$(svnversion)" # Is the working copy clean? echo "${SVNVERSION}" | grep -q "M" case "${?}" in 0) VCS_WC_MODIFIED="1" ;; 1) if [ ! -z "${UNTRACKEDFILES}" ]; then # `svnversion` does not detect untracked files and `svn status` is really slow, so only run it if we really have to. if [ -z "$(svn status)" ]; then VCS_WC_MODIFIED="0" else VCS_WC_MODIFIED="1" fi else VCS_WC_MODIFIED="0" fi ;; 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*) local lastbase="" local 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 local lastbase="${base}" local 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 --xml | sed -n -e 's:::' -e 's:::p')" # Action Stamp VCS_ACTION_STAMP="${VCS_DATE}!$(svn log --xml -l 1 -r "${VCS_SHORT_HASH}" | sed -n -e 's:::' -e 's:::p')" cd "${oldPath}" } # Functions to output data in different formats. # For bash output shOutput() { cat >"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" < "${VCS_TYPE}", "VCS_BASENAME" => "${VCS_BASENAME}", "VCS_UUID" => "${VCS_UUID}", "VCS_NUM" => ${VCS_NUM}, "VCS_DATE" => "${VCS_DATE}", "VCS_BRANCH" => "${VCS_BRANCH}", "VCS_TAG" => "${VCS_TAG}", "VCS_TICK" => ${VCS_TICK}, "VCS_EXTRA" => "${VCS_EXTRA}", "VCS_ACTION_STAMP" => "${VCS_ACTION_STAMP}", "VCS_FULL_HASH" => "${VCS_FULL_HASH}", "VCS_SHORT_HASH" => "${VCS_SHORT_HASH}", "VCS_WC_MODIFIED" => ${VCS_WC_MODIFIED} ); # end ?> EOF } # For ini output iniOutput() { case "${VCS_WC_MODIFIED}" in 0) VCS_WC_MODIFIED="false" ;; 1) VCS_WC_MODIFIED="true" ;; esac cat >"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" < namespace $(echo "${NAMESPACE}" | tr '[:upper:]' '[:lower:]') { const std::string VCS_TYPE = "${VCS_TYPE}"; const std::string VCS_BASENAME = "${VCS_BASENAME}"; const std::string VCS_UUID = "${VCS_UUID}"; const int VCS_NUM = ${VCS_NUM}; const std::string VCS_DATE = "${VCS_DATE}"; const std::string VCS_BRANCH = "${VCS_BRANCH}"; const std::string VCS_TAG = "${VCS_TAG}"; const int VCS_TICK = ${VCS_TICK}; const std::string VCS_EXTRA = "${VCS_EXTRA}"; const std::string VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"; const std::string VCS_FULL_HASH = "${VCS_FULL_HASH}"; const std::string VCS_SHORT_HASH = "${VCS_SHORT_HASH}"; const int VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}; } #endif /* end */ EOF } matlabOutput() { case "${VCS_WC_MODIFIED}" in 0) VCS_WC_MODIFIED="FALSE" ;; 1) VCS_WC_MODIFIED="TRUE" ;; esac cat >"${TARGETFILE}" <"${TARGETFILE}" <"${TARGETFILE}" </dev/null)" ]; then local gitPath="$(git rev-parse --show-toplevel)" local gitDepth="$(pathSegment "${gitPath}")" REPONUM="$((REPONUM + 1))" else local gitDepth="0" fi if [ ! -z "$(hg root 2>/dev/null)" ]; then local hgPath="$(hg root 2>/dev/null)" local hgDepth="$(pathSegment "${hgPath}")" REPONUM="$((REPONUM + 1))" else local hgDepth="0" fi if [ ! -z "$(bzr root 2>/dev/null)" ]; then local bzrPath="$(bzr root 2>/dev/null)" local bzrDepth="$(pathSegment "${bzrPath}")" REPONUM="$((REPONUM + 1))" else local bzrDepth="0" fi if [ ! -z "$(svn info 2>/dev/null)" ]; then local stringz="" local stringx="" local svnPath="$(svn info --xml | sed -n -e "s:${stringz}::" -e "s:${stringx}::p")" # An old enough svn will not be able give us a path; default # to 1 for that case. if [ -z "${svnPath}" ]; then local svnDepth="1" else local svnDepth="$(pathSegment "${svnPath}")" fi REPONUM="$((REPONUM + 1))" else local svnDepth="0" fi # Do not do more work then we have to. if [ "${REPONUM}" = "0" ]; then return fi # Figure out which repo is the deepest and use it. local wonRepo="$(multiCompare "${gitDepth}" "${hgDepth}" "${bzrDepth}" "${svnDepth}")" if [ "${wonRepo}" = "${gitDepth}" ]; then gitRepo elif [ "${wonRepo}" = "${hgDepth}" ]; then hgRepo elif [ "${wonRepo}" = "${bzrDepth}" ]; then bzrRepo elif [ "${wonRepo}" = "${svnDepth}" ]; then svnRepo fi } # Detect which repos we are in and gather data. # shellcheck source=/dev/null if [ -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then # When requested only read from the cache to populate our symbols. . "${CACHEFILE}" else # If a value is not set through the environment set VCS_EXTRA to nothing. : "${VCS_EXTRA:=""}" repoTest if [ -f "${CACHEFILE}" ] && [ "${REPONUM}" = "0" ]; then # We are not in a repo; try to use a previously generated cache to populate our symbols. . "${CACHEFILE}" # Do not overwrite the cache if we know we are not going to write anything new. CACHEFORCE="1" elif [ "${REPONUM}" = "0" ]; then echo "error: No repo or cache detected." 1>&2 exit 1 fi 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}" elif [ "${VAROUT}" = "VCS_ACTION_STAMP" ]; then echo "${VCS_ACTION_STAMP}" else echo "error: Not a valid output symbol." 1>&2 exit 1 fi fi # Detect requested output type and use it. if [ ! -z "${AFILETYPE}" ]; then if [ "${AFILETYPE}" = "c" ]; then cOutput elif [ "${AFILETYPE}" = "h" ]; then hOutput elif [ "${AFILETYPE}" = "xcode" ]; then xcodeOutput elif [ "${AFILETYPE}" = "swift" ]; then swiftOutput 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}" = "json" ]; then jsonOutput elif [ "${AFILETYPE}" = "java" ]; then javaOutput elif [ "${AFILETYPE}" = "javaprop" ]; then javapropOutput elif [ "${AFILETYPE}" = "tex" ]; then texOutput elif [ "${AFILETYPE}" = "m4" ]; then m4Output elif [ "${AFILETYPE}" = "scheme" ]; then schemeOutput elif [ "${AFILETYPE}" = "clojure" ]; then clojureOutput elif [ "${AFILETYPE}" = "rpm" ]; then rpmOutput elif [ "${AFILETYPE}" = "hpp" ]; then hppOutput elif [ "${AFILETYPE}" = "matlab" ]; then matlabOutput elif [ "${AFILETYPE}" = "octave" ]; then octaveOutput elif [ "${AFILETYPE}" = "cmake" ]; then cmakeOutput 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}.tmp" shOutput # Check to see if there have been any actual changes. if [ ! -f "${CACHEFILE}" ]; then mv -f "${CACHEFILE}.tmp" "${CACHEFILE}" elif cmp -s "${CACHEFILE}.tmp" "${CACHEFILE}"; then rm -f "${CACHEFILE}.tmp" else mv -f "${CACHEFILE}.tmp" "${CACHEFILE}" fi fi