Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify history of a git topic branch that used merges to use rebases

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.

like image 935
Raman Avatar asked Jun 20 '12 06:06

Raman


2 Answers

I found the following process seems to work reasonably well, though not perfectly. Some minor conflict resolutions may be required -- see below.

  1. 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
    
  2. Simplify the history of the topic branch by excluding merges:

    git log --no-merges
    
  3. From the above log, determine the branch point (the commit on master before the first topic branch commit, which should be commit F).

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

  5. 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
    
  6. Name HEAD to the rebased topic branch name:

    git checkout -b rebased-topic
    
like image 87
Raman Avatar answered Sep 28 '22 07:09

Raman


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.

like image 23
davidtbernal Avatar answered Sep 28 '22 09:09

davidtbernal