As others have suggested, you can use git add -N
to add dummy entries to the index. Running:
git diff <hash>
will then compare the given to the current work-tree, using the existing index (which now has entries for the untracked files) to decide which work-tree versions to compare to the given <hash>
(<hash>
may be a commit ID, or a tree ID, or a branch name, or anything that Git can resolve to a tree). But this does mess with the index, and as you said, you:
would like to do this in a maximally atomic and robust way
The trick is to use, instead, a temporary index.
The initial commit here has files bar
and foo
; the second commit has file foo
removed. The current work-tree has foo
resurrected and a new file, as shown in git status --short
.
$ git log --all --decorate --oneline --graph
* 11b241c (HEAD -> master) remove foo
* 8527327 initial
$ git status --short
A diffscript
A foo
Note that the initial commit is 8527327
, which is what we pass to diffscript
as an argument:
$ ./diffscript 8527327
diff --git a/diffscript b/diffscript
new file mode 100755
index 0000000..8d5c978
--- /dev/null
+++ b/diffscript
@@ -0,0 +1,6 @@
+#! /bin/sh
+export GIT_INDEX_FILE=$(mktemp) || exit
+rm -f $GIT_INDEX_FILE
+trap "rm -f $GIT_INDEX_FILE" 0 1 2 3 15
+git add -A
+git diff ${1:-HEAD}
index 3ec370c..d319048 100644
--- a/foo
+++ b/foo
@@ -1 +1 @@
-I am a foo
+I was foo, am now resurrected
This diffscript
defaults to diffing against HEAD
. Hence, if we run it with no arguments, we would compare commmit 11b241c
to the index/work-tree (because we git add -A
it mostly does not matter whether we compare to index or work-tree, at this point: they are essentially the same, modulo .gitignore
directives).
The trap ...
line makes sure we remove the temporary index, whether the script is terminated by ^C (signal 2, SIGINT), or a network disconnect (signal 1, SIGHUP) or a QUIT (signal 3, SIGQUIT) or TERMINATE (signal 15, SIGTERM), or just exits normally (0, not a signal, just normal termination).
Annoyingly, Git insists that the file either not exist at all, or have an index file signature—an empty file is not allowed—so we remove the temp file mktemp
makes, so that the git add
step can create it with the right signature.