Skip to content

Instantly share code, notes, and snippets.

@pvdb
Last active July 12, 2023 21:26
Show Gist options
  • Select an option

  • Save pvdb/f2327b17daec2c68d59479a871903f59 to your computer and use it in GitHub Desktop.

Select an option

Save pvdb/f2327b17daec2c68d59479a871903f59 to your computer and use it in GitHub Desktop.
Run the same command against two git revisions, and diff the output
#!/usr/bin/env bash
#
# INSTALLATION
#
# ln -s ${PWD}/git-rev-diff $(brew --prefix)/bin/
# sudo ln -s ${PWD}/git-rev-diff /usr/local/bin/
#
# check command-line options
KEEP=0 ; # transient by default
[ "$1" == '--keep' ] && {
shift ;
KEEP=1 ;
}
FIRST_REVISION="$1" ; shift ;
SECOND_REVISION="$1" ; shift ;
[ -z "$*" ] && {
>&2 echo 'Usage: git rev-diff <rev> <rev> <command(s)>' ;
exit 1 ;
}
# check prerequisites
TMPDIR="${TMPDIR:-/tmp}" ;
[ -z "${TMPDIR}" ] && { >&2 echo "Cannot run without \${TMPDIR}!" ; exit 1 ; }
[ -d "${TMPDIR}" ] || { >&2 echo "\${TMPDIR} is not a directory!" ; exit 1 ; }
# create "lock directory" to be used by `flock`
FLOCK_DIR=$(mktemp -d "${TMPDIR}/git-rev-dir.XXXXXXXX") ;
trap '[ "${KEEP}" == "0" ] && rmdir ${FLOCK_DIR}' 0 2 3 15 ;
# check dependencies
[ -x "$(command -v flock 2> /dev/null)" ] || {
# https://github.com/discoteq/flock
>&2 echo 'Please install flock...' ; exit 1 ;
}
[ -x "$(command -v colordiff 2> /dev/null)" ] || {
# brew install colordiff
>&2 echo 'Please install colordiff...' ; exit 1 ;
}
# only run the script in a "clean" workarea
git diff-index --quiet HEAD || {
>&2 echo "error: cannot rev-diff: You have unstaged changes." ;
>&2 echo "error: Please commit or stash them." ;
exit 1 ;
}
# check git revisions
[ -z "${FIRST_REVISION}" ] && { >&2 echo 'Missing 1st revision' ; exit 1 ; }
git rev-parse --quiet --verify "${FIRST_REVISION}^{object}" > /dev/null || {
>&2 echo "Invalid 1st revision: ${FIRST_REVISION}" ; exit 1 ;
}
FIRST_REVISION_SHA=$(git rev-parse "${FIRST_REVISION}") ;
FIRST_REVISION_SHART=$(git rev-parse --short "${FIRST_REVISION}") ;
[ -z "${SECOND_REVISION}" ] && { >&2 echo 'Missing 2nd revision' ; exit 1 ; }
git rev-parse --quiet --verify "${SECOND_REVISION}^{object}" > /dev/null || {
>&2 echo "Invalid 2nd revision: ${SECOND_REVISION}" ; exit 1 ;
}
SECOND_REVISION_SHA=$(git rev-parse "${SECOND_REVISION}") ;
SECOND_REVISION_SHART=$(git rev-parse --short "${SECOND_REVISION}") ;
# determine which diff to use
if [ -t 1 ] ; then
# use colordiff when interactive!
DIFF_CMD="${DIFF_CMD:-colordiff --unified}" ;
else
# use standard diff otherwise
DIFF_CMD="${DIFF_CMD:-diff --unified}" ;
fi
# record current revision
CURRENT_REVISION=$(git rev-parse --abbrev-ref HEAD) ;
# print a "virtual" diff header to the console
echo "--- git checkout ${FIRST_REVISION} && { $* ; } ;" ;
echo "+++ git checkout ${SECOND_REVISION} && { $* ; } ;" ;
# run command(s) against both revisions and diff the output
[ "${KEEP}" == '0' ] && {
(
${DIFF_CMD} --report-identical-files \
<( flock "${FLOCK_DIR}" \
sh -c "git checkout -q \"${FIRST_REVISION_SHA}\" && { 2>&1 $* ; } ; " ) \
<( flock "${FLOCK_DIR}" \
sh -c "git checkout -q \"${SECOND_REVISION_SHA}\" && { 2>&1 $* ; } ; " ) ;
) | grep -E -v '(---|\+\+\+) /dev/fd' ; # suppress the character special file descriptors...
}
# run command(s) against both revisions, capturing and diffing the output
[ "${KEEP}" == '1' ] && {
FIRST_OUT_FILE="${FLOCK_DIR}/${FIRST_REVISION_SHART}.out" ;
SECOND_OUT_FILE="${FLOCK_DIR}/${SECOND_REVISION_SHART}.out" ;
flock "${FLOCK_DIR}" \
sh -c "git checkout -q \"${FIRST_REVISION_SHA}\" && { 2>&1 $* > ${FIRST_OUT_FILE} ; } ; " ;
flock "${FLOCK_DIR}" \
sh -c "git checkout -q \"${SECOND_REVISION_SHA}\" && { 2>&1 $* > ${SECOND_OUT_FILE} ; } ; " ;
(
${DIFF_CMD} --report-identical-files \
"${FIRST_OUT_FILE}" "${SECOND_OUT_FILE}"
) | grep -E -v '(---|\+\+\+) /dev/fd' ; # suppress the character special file descriptors...
}
# switch back to original revision
git checkout -q "${CURRENT_REVISION}" ;
# That's all Folks!
@pvdb
Copy link
Author

pvdb commented Jan 22, 2018

Note: because the script performs porcelain-level branch switching, it works best if you don't have uncommitted changes in your working tree... if this becomes too much of a roadblock, or if it starts skewing the generated diff, then it's easy enough to add git stash push and git stash pop (with the appropriate options) to work around it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment