2

Suppose we have four commits (with D being the head)

A-B-C-D

If I want to remove (revert) only the changes in commit B but keep the changes made in C and D, how would you do this?

If I do

git revert HEAD~2

I get a conflict. When choosing a resolution, the only two options are to either use the changes made in A, or leave it as is (changes made in D).

Eg.

After commit A, the text file contains

commit 1

after commit B, it contains

commit 1
commit 2

after commit C, it contains

commit 1
commit 2
commit 3

after commit D, it contains

commit 1
commit 2
commit 3
commit 4

So if I now do git revert HEAD~2 I should end up with the text file containing

commit 1
commit 3
commit 4

right? If not, can you please explain why not, as this is exactly what happens when reverting on Mercurial.

Edit: No rewriting history.

Opender Singh
  • 305
  • 2
  • 10
  • I'd use `git rebase` here instead. – raina77ow Feb 09 '14 at 23:21
  • Compare the diff between what HEAD~2 added versus what you change in the end. That's not a trivial reverse applied patch anymore – zapl Feb 09 '14 at 23:25
  • @raina77ow I essentially need to undo the changes in commit B. How can that be done using `git rebase`? – Opender Singh Feb 09 '14 at 23:27
  • @zapl I understand that now. Mercurial produces the right output (shown in the last code block) - what would be the equivalent git command to undo only the change in commit B? – Opender Singh Feb 09 '14 at 23:30
  • hmm, I would start fixing it manually because I have no idea and looking for a better solution usually takes longer :) http://stackoverflow.com/questions/17962955/what-would-happen-if-git-revert-a-commit-that-has-some-other-commit-depends-on-i?lq=1 – zapl Feb 09 '14 at 23:37
  • With "reverting on Mercurial", do you mean using `hg backout`? Or are you just reverting a single file with `hg revert`? – torek Feb 09 '14 at 23:38
  • @torek I have tried with backout but backout/revert in hg essentially do the same thing, no? http://mercurial.selenic.com/wiki/Backout – Opender Singh Feb 09 '14 at 23:53
  • No: `hg revert [-r rev] path` is more like `git checkout [rev] -- path`. – torek Feb 09 '14 at 23:54

1 Answers1

2

OK, let's go through the actual session with git, just so we're sure we are all talking about the same thing. We start with a new repo in a new directory:

$ mkdir /tmp/temprepo; cd /tmp/temprepo
$ git init
Initialized empty Git repository in /tmp/temprepo/.git/
$ echo 'commit 1' > file.txt
$ git add file.txt
$ git commit -m commit-A
[master (root-commit) 1898863] commit-A
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt
$ echo 'commit 2' >> file.txt; git commit -a -m 'commit-B'
[master 1d77fa5] commit-B
 1 file changed, 1 insertion(+)
$ echo 'commit 3' >> file.txt; git commit -a -m 'commit-C'
[master 0bf2ede] commit-C
 1 file changed, 1 insertion(+)
$ echo 'commit 4' >> file.txt; git commit -a -m 'commit-D'
[master 9980dfd] commit-D
 1 file changed, 1 insertion(+)
$ git revert HEAD~2
error: could not revert 1d77fa5... commit-B
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

Let's take a look at the failed revert, to see what git thinks the problem is. Note that I have merge.conflictstyle set to diff3 so that I can see what was in the base version, when the three-way merge fails:

$ git config --get merge.conflictstyle
diff3
$ cat file.txt
commit 1
<<<<<<< HEAD
commit 2
commit 3
commit 4
||||||| 1d77fa5... commit-B
commit 2
=======
>>>>>>> parent of 1d77fa5... commit-B

So here's why git needs help from you, the user: the "base version"—the version that was in commit-A—had one line, commit 1, and then ended. The "change to be removed" was to add one line after commit 1 but at the end of the file, containing commit 2. The "current" version, in HEAD, had four lines, commit 1, commit 2, commit 3, and commit 4.

Git therefore could not remove the last line (commit 4), which did not match the line to be removed; nor could it simply remove the line commit 2, because the file did not then end after that.

What you are supposed to do now is fix the file up yourself, in some editor, to create the "version with the change removed", then git add file.txt and git revert --continue.

Just for the heck of it, let's do this same sequence in Mercurial:

$ cd ..
$ rm -rf temprepo/
$ mkdir temprepo
$ cd temprepo
$ hg init
$ echo 'commit 1' > file.txt
$ hg add file.txt
$ hg commit -m commit-A
$ echo 'commit 2' >> file.txt
$ hg commit -m commit-B
$ echo 'commit 3' >> file.txt
$ hg commit -m commit-C
$ echo 'commit 4' >> file.txt
$ hg commit -m commit-D
$ hg backout -r 1
reverting file.txt
merging file.txt
warning: conflicts during merge.
merging file.txt incomplete! (edit conflicts, then use 'hg resolve --mark')
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges
$ cat file.txt
commit 1
<<<<<<< local
=======
commit 2
commit 3
commit 4
>>>>>>> other

In other words, Mercurial behaves the same as git: it can't un-do "commit-B" without help.

torek
  • 448,244
  • 59
  • 642
  • 775
  • The "at the end of the file" was the icing on the cake. Thank you. – Opender Singh Feb 10 '14 at 00:18
  • @OpenderSingh: yes, the general idea is that git (or hg, or any other patch-applying command, like, say, `patch` :-) ) tries to match the "context" lines as well as the changes. Start-of-file or end-of-file counts as a kind of context line too, though. – torek Feb 10 '14 at 00:37