Last active
December 9, 2020 17:16
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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