17

A few weeks ago (i.e. many commits ago), I made a git commit that touched many files. Most of the commit was great.

However, I'd now like to undo the changes that I'd made to just one of those files, but I don't want to override any changes that had been made to that file after that commit.

Is there a way for me to revert a specific commit but only on one of the files in that commit?

Ryan
  • 22,332
  • 31
  • 176
  • 357
  • Related, but not duplicate (I think): [Reverting a single file to a previous version in git](http://stackoverflow.com/q/2733873/456814). –  May 30 '14 at 04:50
  • See also [How do I reset/revert a specific file to a specific revision using Git?](http://stackoverflow.com/q/215718/456814). –  May 30 '14 at 04:53

2 Answers2

31

Get a patch-ready diff for that file in that commit, and then reverse-apply the patch:

git show <commit-id> -- <path> | git apply -R -

Make sure you like the result, and if so, add and commit with appropriate message.

torek
  • 448,244
  • 59
  • 642
  • 775
  • 2
    thanks but this failed with: `error: : patch does not apply` – Ryan May 29 '14 at 23:30
  • 1
    That implies something in the changes has changed since then, i.e., there's a conflict. You can either: try adding `-3` (aka `--3way`) to use git's merge mechanisms; or add `--reject` (in both cases, to the `git apply` options) but you must then manually resolve the conflicts. – torek May 29 '14 at 23:35
  • I'd just like to point out that the naive way to get a reverse patch would be to simply do `git diff ^ -- | git apply`, but I didn't know that you could just use `git apply --reverse`, that's definitely genius! Why does `git apply -R -` have that final dash at the end though? –  May 30 '14 at 04:30
  • 1
    @Cupcake: `git apply` wants to take a file name; documentation says `- can be used to read from the standard input`. Was not sure off-hand if it would read stdin anyway (I've always fed it files, at least as far as I can recall). – torek May 30 '14 at 04:35
  • Thanks! I used `git show -- | git apply -3 -R -` – Ryan May 30 '14 at 14:16
  • This is awesome! Thanks! – Yuri Pozniak Mar 19 '21 at 02:08
6

Torek's answer is definitely better (and simple!), but for the sake of completeness, I wanted to add a few alternative (some naive) solutions to this problem.

Solution 1: Reverse Patch (naive, compared to Torek's solution)

This is very similar to Torek's solution. To undo the changes to a specific file that were made by a specific commit, just get a reverse diff of that commit by reversing the arguments to git diff:

git diff <commit> <commit>^ -- <filepath> | git apply

Normally, to get the changes introduced by <commit>, you use the order

git diff <commit>^ <commit>

but by reversing the order, you end up getting the inverse of those changes, which is a valid diff that can be used as a patch!

Solution 2: Revert All Files, then Only Commit Changes to Specific Files (very naive)

A more naive solution would be to revert the commit that added the changes that you want to undo, but don't commit the revert. Then just remove all the changes to the other files from the index and working copy:

git revert --no-commit <commit>

# Unstage all changes that revert all files
git reset -- .

# Stage and commit just the reversion changes that you want
git add <yourFile>
git commit -m "Revert changes to <yourFile> from <commit>"

# Undo all reversions changes to the other files
git checkout -- .

This is necessary because git revert doesn't revert changes to individual files, it currently only reverts entire commits.

Community
  • 1
  • 1