Search code examples
gitundogit-merge

Undo a Git merge, but keep later changes, and rewrite history


I have a bunch of branches, each with different features. Usually I'll have some extra branch "not_master" which contains master+feature A like so:

(not_master)     a--b--c--d--e---M--x--y--z
(feature A)           --h--i--j-/

Sometimes I want to unmerge feature A, but keep commits x,y,z in "not_master".

In other words, I would like this:

(not_master)     a--b--c--d--e--x--y--z

I see that I can do a git revert -m 1 M which will add a commit to the end that reverts my changes, but I don't really want to do that since these commits haven't been published yet so adding more commits makes the history even harder to read.

Others suggest just doing a git reset --hard M, but that will dump the changes of x,y,z. Am I just thinking about this the completely wrong way? Should I just git reset --hard M and cherry pick x,y,z?


Solution

  • git rebase will do what you want. git rebase -i <commit e> should work: it will open an editor, and you delete the merge commit and save.

    You should also be able to directly specify rebasing x..z onto e, but sorting out the semantics of the command-line is a bit hairy. If I'm reading the man page right, It should be git rebase --onto <commit e> <commit M> not_master.

    EDIT: Tested and appears to work:

    mkdir foo; cd foo
    git init
    touch initial
    git add initial
    git commit -m 'initial'
    git checkout -b topic
    touch topic
    git add topic
    git commit -m 'topic'
    git checkout master
    touch master
    git add master
    git commit -m 'master'
    git tag pre_merge
    git merge topic
    git tag merge
    touch post_topic
    git add post_topic
    git commit -m 'post topic'
    git rebase --onto pre_merge merge master
    

    results in a history of

    initial -- master -- post topic  (master)
          \
           \-- topic                 (topic)