Skip to content

Instantly share code, notes, and snippets.

@Fordi
Last active December 9, 2020 17:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Fordi/8f1828efd820181f24302b292670b14e to your computer and use it in GitHub Desktop.
Save Fordi/8f1828efd820181f24302b292670b14e to your computer and use it in GitHub Desktop.
vc-rev: similar to git-hash, except it works on svn repositories as well. Minimal dependency on sqlite3 for SVN repos
#!/bin/sh
# From the Stack response:
# http://stackoverflow.com/a/33133769/353872
#
# Also acts as a low-complexity example of how to flexibly parse the argument
# list in raw sh without external programs or a lot of fuss.
# For use in usage() and in log messages
SCRIPT_NAME="$(basename $0)"
# Usage function: tells the user what's up, then exits. ALWAYS implement this.
# Optionally, prints an error message
function usage() {
if [ -n "$1" -a $VERBOSE -gt -1 ]; then
echo "[$SCRIPT_NAME] ${@}" >&2
fi
log "Get HASH of Git repositories at current branch and revision"
log "Usage: $SCRIPT_NAME [-v] PATH [PATH...]"
log " -v|--verbose Print a log of the references as they are followed"
log " PATH Paths for which to get revision hash"
exit 1
}
# Write a message to stderr
function log() {
echo "${@}" >&2
}
# Write an informative message with decoration
function info() {
if [ $VERBOSE -gt 0 ]; then
log "[$SCRIPT_NAME] ${@}"
fi
}
# Write an error and exit
function error() {
if [ $VERBOSE -gt -1 ]; then
log "[$SCRIPT_NAME] Error: ${@}"
fi
exit 1
}
VERBOSE=0
# Create an array for non-flag arguments
ARGS=()
# Loop over arguments; we'll be shifting the list as we go,
# so we keep going until $1 is empty
while [ -n "$1" ]; do
# Capture and shift the argument.
ARG="$1"
shift
case "$ARG" in
# User requested help; sometimes they do this at the end of a command
# while they're building it. By capturing and exiting, we avoid doing
# work before it's intended.
-h|-\?|-help|--help)
usage
;;
# Make the script more verbose. VERBOSE is used by info() and
# error() above
-v|--verbose)
VERBOSE=$((VERBOSE + 1))
;;
# Make the script quieter
-q|--quiet)
VERBOSE=$((VERBOSE - 1))
;;
# All arguments that follow are non-flags
# This should be in all of your scripts, to more easily support filenames
# that start with hyphens. Break will bail from the `for` loop above.
--)
break
;;
# Something that looks like a flag, but is not; report an error and die
-?*)
usage "Unknown option: '$ARG'"
;;
#
# All other arguments are added to the ARGS array.
*)
ARGS=(${ARGS[@]} "$ARG")
;;
# An alternate implementation, if your script does not accept unflagged
# arguments, is:
# *)
# echo "$SCRIPT_NAME Invalid argument: '$ARG'" >&2
# usage
# exit 1
# ;;
esac
done
# If the above script found a '--' argument, there will still be items in $*;
# move them into ARGS
while [ -n "$1" ]; do
ARGS=(${ARGS[@]} "$1")
shift
done
# For this script, you must specify at least one item that ends up in ARGS;
# Report an error, print usage, and die
# An exception is when the current directory is a repository; then assume "."
if [ ${#ARGS[@]} == 0 ]; then
if [ ! -d ".git" -a ! -d ".svn" ]; then
usage "No repository specified; cannot continue"
fi
ARGS=(${ARGS[@]} ".")
fi
# Loop through the values of ARGS
# This is the correct way to loop through a shell array so that whitespace in
# the arguments is preserved; if you looped with `for REPO in $ARGS`, you'd get
# your REPOs split by spaces - which is almost certainly not what you want to
# do.
for REPO in "${ARGS[@]}"; do
# Store the user's working directory, then change to the requested directory
WD="$PWD"
cd "$REPO"
# Check for the presence of a directory named '.git' If it's not there,
# report the error and die
if [ ! -d ".git" -a ! -d '.svn' ]; then
error "$REPO is not a Git repository"
fi
# Git's structure is relatively simple. `.git/HEAD` contains a reference to
# the location of the current HEAD pointer. References are formatted as
# "ref: {path}", where `path` is relative to `.git`. If `ref:` is not present,
# the string is the hash, representing a path relative to .git/objects
# (actually, {path:0:2}/{path:2:}), to the packed delta of the latest version
# of the object (which could be a file or directory or whatever).
# For this script, we only care about refs and the hash itself, not the content
# of objects
if [ -d ".git" ]; then
# Start with a pseudo-reference to HEAD
HASH="ref: HEAD";
# As long as HASH starts with "ref:" (which is illegal for hashes)
while [ "${HASH:0:4}" == "ref:" ]; do
# Capture the HASH
REF="${HASH:5}"
info "Following reference chain to $REF"
if [ ! -f ".git/$REF" ]; then
error "Failed to follow reference: '.git/$REF'! This implies that" \
"this Git repository is broken!"
fi
HASH="$(cat ".git/$REF")"
done
# Print the final value of HASH, which should be the repository's HEAD hash
echo "git:$HASH"
fi
# Unfortunately, we can't avoid external dependency for SVN, which uses an
# sqlite3 database for storing its information. At least sqlite3 is small
if [ -d ".svn" ]; then
REV="$(sqlite3 '.svn/wc.db' 'SELECT MAX(revision) from NODES_CURRENT;')"
echo "svn:$REV"
fi
# Restore the working directory, so that the next call to `cd` is relative
# to the user's working path
cd "$WD"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment