4

I think my question is close to this one, but I'm using git.

So, let's say I have

Commit1 changed file1.c and file2.c

Commit2 changed file2.c, file3.c, file4.c

Commit3 changed file1.c , file2.c and file3.c

and so on...

Then, I would like to revert only the changes that Commit2 made in file2.c, but, try to keep the changes that Commit3 made in this file...

What you think? There is some way to do it?

Thank you.

Community
  • 1
  • 1
Marcos
  • 47
  • 6

3 Answers3

5

In general, to back out an entire commit, you would use git revert (as you probably already know). Your issue here is that you want to back out only some of the changes in an older commit. But it turns out this is easy, because:

  • git revert has the same effect as applying a patch in reverse (git show or git diff with specific commit IDs as needed, and then git apply -R the resulting patch) and then committing; and
  • git diff allows you to choose which file(s) get diff-ed.

So, for a regular (single parent) commit, you can simply:

git show <commit-id> -- <path>

to get the "interesting" change from <commit-id>, and then pipe (or otherwise feed) the result to git apply -R:

git show HEAD~3 -- <path> | git apply -R

or in this case:

git show commit2 -- file2.c | git apply -R

For a merge commit, git show will produce a combined diff, which is not what you want, so you should use git diff (or git diff-tree) with two specific commit-IDs. Note that you can inspect the diff before (or after) reverse-applying, since git apply does not make the new commit itself (so you will have to do that on your own).

torek
  • 448,244
  • 59
  • 642
  • 775
  • wouldn't `git diff Commit2..Commit1 -- file2.c` be enough to get exactly the change which he/she needs to `apply`? – user3159253 Dec 09 '14 at 20:34
  • @user3159253: sure, if you reverse the diff you don't have to reverse the apply. I find it easiest to use `git show` to get the patch for most cases, though, and that leaves the diff as "old vs new" instead of "new vs old", so then you have to reverse-patch. – torek Dec 09 '14 at 20:36
  • @torek your answer seems nice, but it's a little bit hard to me understand it... Could you please use the names I used in my example? For example, `` is equal to `file2.c` in this case? thanks – Marcos Dec 09 '14 at 20:43
  • I'm getting this error: _error: patch failed: file2.c:1 error: file2.c: patch does not apply_ – Marcos Dec 09 '14 at 22:41
  • 2
    @Marcos: that means some subsequent change has modified the area you're attempting to revert. You can ask git to do a three way merge (`git apply -3`), or use `--reject` (to make git act like `patch` with its `.rej` files). In any case you'll definitely have to look closely to make sure the "undo" worked. Think of it as "the patch I'm trying to undo says to repaint green with blue, so git will repaint blue with green to undo that; but last week Joe repainted blue with red, so git can't find the blue to repaint with green." It's going to need help, or at least supervision. – torek Dec 10 '14 at 02:17
4

You may try to revert Commit2 (that is, apply a new git commit, whose changes would be opposite to the changes made in Commit2) but without committing. There's a instant menu entry in gitk for example. In this new commit you leave only changes made to file2 and commit the result.

Alternatively, you could simply do something like

git diff Commit2..Commit1 -- file2.c > revert.patch
git apply revert.patch

it might be useful to take a look on the patch before applying it because commits after Commit2 may introduce some conflicts with the changes to file2.c from Commit2.

user3159253
  • 16,836
  • 3
  • 30
  • 56
  • It's returning an error: _error: patch failed: file2.c:1 error: file2.c: patch does not apply_ – Marcos Dec 09 '14 at 22:45
  • `cat revert.patch` is printing **diff --git a/file2.c b/file2.c index 36156c2..bb5f30e 100644 --- a/file2.c +++ b/file2.c @@ -1,2 +1 @@ // Comented in the FIRST commit -// this line was inserted in the second commit** – Marcos Dec 09 '14 at 22:46
  • 2
    Marcos that's what I said. If the changes on the file conflict with each other, then you necessarily get the conflict and have to manually resolve it. Likely now you're just playing in a sandbox repository, right? then try to switch to a more complex example when changes to file2.c don't contradict to each other (e.g. made in different parts of the file). Otherwise you always will be faced with errors like the one above, and have to make appropriate changes manually – user3159253 Dec 10 '14 at 01:28
  • 1
    I figured out it after comment here, I'm sorry. As you said, it's a conflict because I changed the same line. And yes, I'm doing it in a local "toy" repository to test and understand well before do it in the my real project. Anyway, thank you. – Marcos Dec 10 '14 at 02:07
1

Do you want to do revert?

Or just want to modify it. If so, you can do it with

git rebase -i HEAD^^

Next, an editor will open. In the editor modify 'pick' to 'edit' on Commit2 like

pick abcdefg Commit3
pick jikdvuf Commit2

to

pick abcdefg Commit3
edit jikdvuf Commit2

Then, you can modify your commit. So now modify the file. You may want to do git checkout FILENAME. After that

git add FILENAME
git rebase --continue

Now you successfully modified the file on Commit2.

shirakia
  • 2,369
  • 1
  • 22
  • 33