Friday, November 9, 2012

Merging most of one git branch into another, without history rewrite

This little post explains how to partially commit a feature branch into a stable branch in git, without rewriting the history of any of the branches.

Having worked on a feature branch for a few weeks, I am left with 50+ commits on top of the common ancestor point. Yet I cannot merge all those changes back to stable, as some are still feature branch specific. And the feature branch is public (pushed to a remote repo) and used by our CI server. So I don't really want to rewrite its history.


S1                                (stable)
   \                            
    C1-F1-C2-C3-F2    (feature)

Basically I want to merge into stable C1, C2, C3 but keep F1, F2 in my branch.



So to be able to merge most of my changes into the stable branch, I am going to create an intermediate branch containing the changes I can have.

In effect I will have 3 branches

  • the stable branch
  • the feature branch
  • the stable_feature_common branch: where I keep work in sync between the 2 others.
Here's how I did it.

1. Find the common ancestor between my normal branch and my feature branch

$ git merge-base stable feature
0e43ddd8857fdcff0f50258cde194b6dd2ef26ff

2. find how many changes we had

$ git log --pretty=oneline 0e43ddd8857fdcff0f50258cde194b6dd2ef26ff..HEAD | wc -l
57

3. create a new stable_feature_common branch to contain my changes

$ git checkout -b stable_feature_common 0e43ddd8857fdcff0f50258cde194b6dd2ef26ff

4. take note of the current HEAD of my feature branch

 f19869fd6ef69186bb2e8172833b09521e7aacb5

5. create a rebased copy of my feature branch into my common branch.

$ git rebase --onto stable_feature_common stable_feature_common feature

When using git rebase --onto 1 2 3, git rebase all changes between 2 and 3 onto 1. As consequence of this operation, 3 becomes the new 1. In my case, the resulting work is referenced as feature. We will have to rename it.

Note the new HEAD of the feature branch (for the future feature_common branch)

697176ed7370099a9e559b6c82f3ac12caa36d47

6. force the feature branch reference back to where it was

$ git checkout stable_feature
$ git reset --hard f19869f

7. force the feature_common branch reference

$ git branch -f stable_metro_common 697176ed7370099a9e559b6c82f3ac12caa36d47

Now feature_stable_common and feature are identical code wise (but with different commit ids and istory caused by the rebase). I am going to edit feature_stable_common since the branch point commit and remove/edit the things I need only in that branch.

$ git checkout stable_feature_common
$ git branch -i 0e43ddd8857fdcff0f50258cde194b6dd2ef26ff

[lots of editing/commiting]

8. merge feature_stable_common into feature and stable (to easily track future changes)

$ git checkout stable_feature
$ git merge stable_feature_common

$ git checkout stable
$ git merge stable_feature_common

I now have

S1                     S2           (stable)
  \                      /
    C1´-C2´-C3´                (stable_feature_common)
   \                        \
    C1-F1-C2-C3-F2-M    (feature)

C1´, C2´, C3´ are rebased and potentially edited versions of C1, C2 and C3
S2 is my merged common into stable
M is my merged common into feature

This should allow me to track the future commits and merge them back properly.

Some links I've found entertaining: