0

For revert the changes to a file in a commit, from https://stackoverflow.com/a/2620822/156458

#!/bin/bash

function output_help {
    echo "usage: git-revert-single-file <sha1> <file>"
}

sha1=$1
file=$2

if [[ $sha1 ]]; then
git diff $sha1..$sha1^ -- $file | patch -p1
else
output_help
fi

To what files does patch -p1 apply the patch file (i.e. the output of git diff $sha1..$sha1^ -- $file)?

Does it apply to all the files in the working directory?

But the working directory might not be the same as the commit $sha1. So does it make sense to apply the difference between $sha1^ and $sha1 to the working directory, which might or might not be the same as $sha1?

Thanks.

4

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 git (a/ and 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 /usr/share/cli-common/packages.d/$POLICY.installcligac
+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.)

3
  • Thanks. (1) Do you mean that the stdout output of git diff $sha1..$sha1^ contains both "the difference for changing $sha1 to $sha1^" and "the files which patch can apply the difference to"? (2) Are "the files which patch can apply the difference to" within $sha1? (3) When running patch -p1 with input from the output of git diff, does patch -p1 modify the commit $sha1, or does patch -p1 store the results in working directory without modifying the commit $sha1? (4) Does it make sense to modify or delete a commit? If yes, what commands can modify and delete a commit? – Tim Apr 7 '17 at 20:48
  • You’re asking me to write a book there... I’ve added an example of a patch in the hope of answering (1). I’m not sure what you mean by (2); patch can apply the patch to any file satisfying the constraints given in the patch (filename and change context). As for (3), you can’t modify a commit; the changes are applied to the current working directory without committing anything. – Stephen Kitt Apr 8 '17 at 19:01
  • Regarding (4), again you can’t modify a commit in git, but you can replace commits (typically using commit --amend), and re-organise commits (with git rebase). The SHA-1 which identifies a commit is based on the commit’s contents, so you can’t ever modify that (short of introducing collisions of course). But replacing and re-organising commits is a common occurrence when developing with git, especially with tools such as Gerrit. – Stephen Kitt Apr 8 '17 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.