-1

I already know how to revert a merge commit including selecting parents.
But now I'm facing a different situation where there is a new commit after the merge commit.

Looking at :

enter image description here

You can see that after merge commit "2", there is a new commit "1".
All I want is to revert the merging of "3" to the branch master. ( I know that "2" is a merge commit).

Question:

How can I revert the merge commit with the red arrow? I've numbered the relative commits so it would be easier to ref in an answer.

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • It depends what "revert" means. See https://stackoverflow.com/a/60747937/341994 for some possibilities. But in general trying to rewrite history on `master` is a Bad Smell; you can do it only if you are the only person who ever uses this remote. – matt Apr 04 '20 at 14:50
  • Revering is not rewriting history. Rebase is – Royi Namir Apr 04 '20 at 14:52
  • 1
    Agreed. That is why I asked what you mean by "revert". If you really mean `git revert` then yes, that's the way to go. – matt Apr 04 '20 at 14:56
  • @matt i must be missing something here. If i have abcd as commits. So d is the diff between d and c . Right? If so , then i must also keep 1 as patch , revert it , revert the merge with parent 4 , and then apply patch. Right? – Royi Namir Apr 04 '20 at 14:58
  • "So d is the diff between d and c . Right?" Wrong. Commits are not diffs. This is git, not svn. – matt Apr 04 '20 at 15:26
  • When using `git revert` on a *merge* commit, you must tell Git which *parent* to use with the `-m` argument. This is because (as @matt notes) Git doesn't store diffs, it stores whole snapshots. The `-m` option tells Git which parent to use to *create* a diff so that Git can reverse the changes. – torek Apr 04 '20 at 21:42
  • When using `git revert` on a *non* merge commit, such as #3 above, you *must not* provide a `-m` argument: `git revert` complains if you do. This has been sort of smartened recently because it's annoying when reverting a block of commits. – torek Apr 04 '20 at 21:44
  • In any case, `git revert` *adds a new commit*. In Git, commits *are* history, so this just adds more history. If you want the history to show that the merge never happened, you must do the history-rewriting noted in the accepted answer. This amounts to: "copy all the commits you want to keep, that look different in the rewritten history, to new-and-improved ones; then force the branch name to hold the hash ID of the latest *new* commit, instead of the latest *old* one." – torek Apr 04 '20 at 21:46
  • @torek, say i revert 2. Now it will be on top of 1. But what about 1 ? 1 was created in the past AFTER 2. So now if i revert 2 , how 1 kicks in here ? – Royi Namir Apr 05 '20 at 03:55
  • Basically, commit 1 *doesn't* kick in, *unless* you get merge conflicts while attempting to revert commit 2. In the case of merge conflicts, you must resolve them yourself, and you should take into account, at this time, what you want to keep from commit 1 and what you want to discard. It's possible that the reverted commit won't build because commit 1 depends on stuff *from* commit 2, that Git can't detect (because the lines do not touch); that, too, is your own responsibility. – torek Apr 05 '20 at 04:07
  • @torek but what about if there is no conflicts? If i revert 2 , will 1's still appear in my working folder ? Or will 1 be destroyed becuase weve reverted to 2 (which is older than 1) ? – Royi Namir Apr 05 '20 at 04:13
  • Revert does not revert *to*. Revert compares a commit with its parent—this produces a diff listing, showing what changed between that parent and that commit—and then, in effect, *undoes* that change. The *latest* commit has a snapshot, and that snapshot is where this "undo what the commit did" changes are applied. This glosses over the fact that both cherry-pick and revert use Git's *merge engine* internally, so there are actually two diffs, but for non-conflict cases, it makes sense. – torek Apr 05 '20 at 04:19
  • Your *working tree*, if that's what you mean by "working folder", is not particularly relevant here: it *must* match your index and your index *must* match your current commit. Remember, Git is about *commits* and you are reverting a commit to make a new commit from the current commit. (These rules do go out the window if you use `git revert -n`, however.) – torek Apr 05 '20 at 04:20
  • @torek I've made a simple test: I've created a `seed.txt` file . and then I committed `1` in line `1` , committed. `2` at line `2` +commited, `3` at line `3`+commited, `4` at line `4`+commited. Now when I clicked on commit `2` and reverted, it showed me conflicts: https://i.imgur.com/B0W8t1t.jpg. I've expected to see `1 \n \n 3 \n 4`. because as you've said, we've reverted `2`. but I'm getting conflicts. why doesn't git remove only `2`? – Royi Namir Apr 05 '20 at 05:41
  • 1
    The problem here is that, during the merge operation, Git sees that one set of changes is "delete line reading `2` at line 2 and end of file" and the other change is "add 2 lines, reading `3`, then `4`, after line 2 / at end of file". These changes overlap: both try to alter "line 2". If you made non-overlapping changes—if the first commit had 100 lines, and the 2nd added a line `2` at line 2, and the third and fourth commits changed only lines much further down, for instance—you would not get a conflict, because the differences wouldn't overlap like this. – torek Apr 05 '20 at 07:46

2 Answers2

3

I tend to use Tower as my git client here you can see me directly reverting an older commit.

Without rewriting history:

First revert the thing you want to get rid of locally:

 git revert sha-of-2 -m X

Where X is the parent number (likely 1 in this case). See here for details.

Resolve any merge issues that come from that. Resolving these potential conflicts will make sure that 1 is applied correctly without having to revert it first. then commit and push.

The result will be:

-- 4 -- 2 -- 1 -- '2 <- master
        /
-- 3 --/

where '2 is the inverse of 2.

With rewriting history:

WARNING: force pushing to master after rewriting history may have severe negative impact on other users of the same branch.

Do an interactive rebase:

 git rebase -i sha-of-2

drop the merge commit, keep commit 1, then force push master.

The result will be:

-- 4 -- '1 <- master

-- 3

Where '1 is a new commit with the original changes of 1 plus any conflicts you had resolved.

Rewriting history using a new branch

WARNING: force pushing to master after rewriting history may have severe negative impact on other users of the same branch.

This may be the easier to understand. It does the exact same thing as the rebase example able, but instead of applying rebase-fairy-dust, you do the hard work yourself.

Instead of rebasing you could re-do these changes yourself. Reset to 4, create a new branch, cherry pick 1, reset master to '1 and force push.

     /- '1 <- master
    /
-- 4 -- 2 -- 1
        /
-- 3 --/

git reset --hard sha-of-4
git checkout -B newmaster
git cherry-pick sha-of-1
# fix conflicts if any
git checkout master
git reset master --hard newmaster
git push --force

Where '1 is the cherry-picked version of 1.

jessehouwing
  • 106,458
  • 22
  • 256
  • 341
  • Isn't 1 is affected by 2 which im about to revert? I always thought that if i want abcd to become ab then i must revert d and revert c. But now i dont know how 1 will be. Becuase 1 is on top of 2 – Royi Namir Apr 04 '20 at 14:46
  • Isnt the first part exactly what i wrote? – D. Ben Knoble Apr 04 '20 at 14:58
  • You mean if 1 and '2 conflict. Not 2. – Royi Namir Apr 04 '20 at 15:12
  • Why wasi under the impression that i can never revert 2 without revert 1 first ? I was tought i should treat it like onion . From outside to inside. Am i wrong? – Royi Namir Apr 04 '20 at 15:17
  • You don't have to, though if you pick a very old commit to revert, git may just have you do it manually because the inverse changes no longer apply and cause conflicts everywhere. It also depends on the delta between `1` and `2`. If all else fails, replaying the changes by reverting them all may be an easier to understand option. – jessehouwing Apr 04 '20 at 15:19
0

At the command line it should be as simple as git revert 2, though I know sometimes reverting merges can be difficult.

D. Ben Knoble
  • 4,273
  • 1
  • 20
  • 38
  • What about commit number 1? – Royi Namir Apr 04 '20 at 14:22
  • What about it? @RoyiNamir you’ll get a new commit on top of commit 1 that reverts commit 2. Commit 1 will still be there – D. Ben Knoble Apr 04 '20 at 14:26
  • No. Read this https://stackoverflow.com/a/1470452/859154 . Commits should be revert in desc order one by one. – Royi Namir Apr 04 '20 at 14:33
  • Sorry, not sure I follow: the question asks how to revert the merge. The answer provides it. The link you gave is specifically for reverting a series of commits, which is not what you asked. The manpage for git-revert clarifies it’s operation. – D. Ben Knoble Apr 04 '20 at 14:38
  • I guess i wasnt clear enough. Do you agree that if i want master to be as if it was never been merged with branch stage, i must revert also 1? (And then reapply it) – Royi Namir Apr 04 '20 at 14:42