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?
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.