Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rebasing to change parent of a merge commit

Tags:

git

git-rebase

Suppose I've got the following history, where the top line is the master branch, the lower one is a feature branch that's merged with master at one point, and D just reverts C (which means that the working directory is the same in B and D).

A---B---C---D           master
 \   \
  E---F---G             feature

I want to add C and its reversion D to the history before the merge in F, like this:

A---B---C---D           master
 \           \
  E-----------F'--G'    feature

I don't want to change E (which is actually a long series of commits).

git rebase --onto D B (as suggested here) results in merge conflicts (with or without --preserve-merges).

Is there a way to accomplish what I want?

like image 557
Travis Brown Avatar asked Feb 17 '15 23:02

Travis Brown


1 Answers

Most methods will be somewhat painful. There's a moderately painless version using git filter-branch, except that filter-branch itself is painful. :-) (You'd filter commits F and G and write a commit-filter that substitutes in the new parents you want for F', and let the filter-branch operation replace the parentage for G.)

I think the simplest method that does not resort to low level commands is just to make a new merge, then rebase G onto the new merge. The new merge may have conflicts but we don't care, we just want to take the old merge's tree, which we can do like this:

$ git checkout <sha1>  # Use D or E's sha-1 here.
                       # Note: whichever you use will
                       # be the first parent of our new
                       # merge; choose based on that.
$ git merge --no-commit <sha1>  # use the remaining sha-1 here
[ignore resulting stuff]
$ git rm -rf .         # Note: assumes you're in top dir of work tree
$ git checkout <sha1-of-F> -- .
$ git commit           # Create merge commit F'

The first checkout gets you on a detached HEAD with one SHA-1, the merge --no-commit starts the merge process with the other SHA-1, the git rm -rf . throws away the merged tree and any conflicts, and the git checkout <id> -- . fills in the index and work-tree from the previous merge. The final git commit creates merge F' with the same tree as merge F, but with different parents.

At this point (still with a detached HEAD) you can rebase (or cherry-pick) commit G (or many commits G), then force your branch to point to the tip of the new graph. I'd suggest using git rebase ... --onto HEAD but I have not tested this with a detached HEAD and there's at least one way it might go wrong (resolving HEAD to an ID too late).

The low level git commit-tree command may actually be even simpler. Andrew C wrote the correct command in a comment, although you have to spell out the branch name with git update-ref. [Edit: maybe not quite correct, the two parents you want are D and E, not D and B. Again, put the one you want as first-parent first.]

The advantage (?) of using the more familiar commands is that they're, well, more familiar.

like image 199
torek Avatar answered Oct 23 '22 06:10

torek