git diff $sha1..$sha1^ produces a patch which reverts the
$sha1 commit (it lists the differences between that commit and its parent). If
$file is specified, it limits that patch to the changes made to
$file in the given commit.
That patch is then fed to
patch -p1, which will strip the fake directory names used by
b/), and attempt to apply the patch to whichever files are listed in the patch (i.e.,
$file if it was named and changed in the given commit, or all the files changed in the given commit, including files in sub-directories). If the files present in the current directory and its sub-directories are substantially different (or by extension, missing),
patch will fail to apply the patch.
This is made possible by the fact that patches in unified format, as produced by
git diff (and
diff -u), include the names of the files being patched, and context for the patch. Here’s an example (not from
git, but it shows the idea):
diff -ur cli-common-0.9+nmu1.orig/policy-remove cli-common-0.9+nmu1/policy-remove
--- cli-common-0.9+nmu1.orig/policy-remove 2015-02-25 21:34:08.000000000 +0100
+++ cli-common-0.9+nmu1/policy-remove 2017-04-08 20:47:09.029065259 +0200
@@ -11,4 +11,4 @@
#echo "Removing GAC policy file ($POLICY) from available GACs"
/usr/share/cli-common/gac-package-remove $POLICY > /dev/null
+rm -f /usr/share/cli-common/packages.d/$POLICY.installcligac
This patch says that it’s modifying the file named
cli-common-0.9+nmu1.orig/policy-remove to produce the file named
cli-common-0.9+nmu1/policy-remove. The change itself starts at line 11 and covers 4 lines, including context (that’s the
@@ -11,4); in the target, the changed lines are in the same position (
+11,4 @@). There are three lines of context above the change, then the change itself, deleting a line starting with
rm and adding a line starting with
rm -f. When
patch applies this, it will look for the appropriately-named file (after removing path components if instructed to with a
-p option), and compare the context in the file with the patch; only if the context matches (within a few lines, depending on the fuzz options) will the change be applied.
The whole point of this script is to try to revert the changes made to a single file in the given commit (hence its name). Whether that’s possible or not depends on the changes that have been made to the file since that commit; but it’s often quite useful in practice. (To revert a complete commit you’d use
git revert instead.)