Last active
July 12, 2023 21:26
-
-
Save pvdb/f2327b17daec2c68d59479a871903f59 to your computer and use it in GitHub Desktop.
Run the same command against two git revisions, and diff the output
This file contains hidden or 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
| #!/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! |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 pushandgit stash pop(with the appropriate options) to work around it.