Our git development workflow is that topic branches are continuously rebased on the latest master until they are merged.
However, a new developer has created topic branches in which he has done several merges of master into his topic branches to keep them up-to-date.
A---B---C---D---E topic
/ / /
F---G---H---I master
While merging this topic branch to master would be perfectly correct, it results in a very messy history. I want to convert these topic branches into a clean linear rebased history which can be merged cleanly into master with a single --no-ff
merge commit i.e.:
A'---B'---E' topic
/
F---G---H---I master
Ideally, there would be some git-fu that would allow me to do the rebase, taking the commits on the topic branch as-is, while automatically applying the merge conflict resolution information already available in the topic merge commits such as C and D.
I know I can simply apply the patch of "git diff master..topic" and then use rebase to work backwards and manually split apart the single patch into individual commits, but is there a simpler and more elegant approach?
I've tried straight git rebase
and git rebase -p
commands with no luck.
I found the following process seems to work reasonably well, though not perfectly. Some minor conflict resolutions may be required -- see below.
Ensure the topic branch is up-to-date with the latest master by doing a final merge from master, if not done already:
git checkout topic
git merge master
Simplify the history of the topic branch by excluding merges:
git log --no-merges
From the above log, determine the branch point (the commit on master before the first topic branch commit, which should be commit F).
Rebase the topic branch onto master, ignoring merges (which is the default, i.e. do not use the --preserve-merges
/ -p
option), resolving any conflicts.
git checkout master
git rebase --onto HEAD F topic
I found that during rebasing, often a conflict would result in which a file was in a conflicted "Both Modified" state, but it contained no conflict markers, so a simple git add
and git rebase --continue
was sufficient to resolve the conflict and continue. I believe git was using the previous resolution to resolve the conflicts.
Also, in some more complex branches, multiple git rebase --onto HEAD ...
commands will be required, or alternatively git cherry-pick
can be used to pick out individual commits. Simply work through the log given in Step #2, rebasing ranges onto HEAD and/or potentially cherry-picking individual commits as necessary.
The current HEAD should now represent the rebased topic branch. Verify that the result matches the original topic branch by checking that the diff results in no output:
git diff topic..HEAD
Name HEAD to the rebased topic branch name:
git checkout -b rebased-topic
Raman's answer pretty much worked for me, but I thought I'd add more detail around step 4, which was tricky for me..
I had a repo that was more complicated, it was like this
Q-R-S-T-U-V-W-X
/ / / /
A-B-C-D-E-F-G-H-I-J-K
I wanted this:
Q'-R'-S'-T'-U'-V'-W'-X'
/
A-B-C-D-E-F-G-H-I-J-K
What worked was to do git rebase --onto HEAD Q R
, then git rebase --onto HEAD S U
, so basically grabbing the ranges of commits between the places where I merged master
in to topic
. The whole time I was doing this, I was in detached head state, so don't worry if you see that. There were a few times I had merge conflicts, but they were all legitimate conflicts that I'd expected to see.
Finally, I had a branch that looked about right (Step 6 in Raman's answer), but for some reason I had to rebase onto master again to actually move the commits. I don't know why I had to do that, but it went smoothly.
I think doing it this way is basically equivalent to cherry picking, so it might be simpler to do that instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With