Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How-to git backport (rebase/cherry-pick) an already merged branch

In our Git process, "master" is the integration branch for topic and fix branches for the current release cycle, but we also maintain a "stable" branch where we have to backport carefully some of our fixes already successfully tested on master.

All the difficulty is that the branch has already been merged back in "master" (else it is really easy with rebase --onto)

  • We don't want to change the process the other way because a) we don't want to fix everything in the "stable" branch, and b) we sometimes have to make some changes to the "stable" branch that we don't want to merge in "master".
  • Clearly, we cannot merge the fix into the "stable" branch because this will backports many unwanted features.

Graph of the initial situation I describe :

          I--J (stable)
         /
        /
       /
- A - B - C - D - E - F - G  (master) 
              \      /
               X -- Y (fix/123)

Graph of the kind of situation we want to reach :

          I--J (stable)
         /    \
        /      X'- Y' (fix/123-stable)
       /
- A - B - C - D - E - F - G  (master) 
              \      /
               X -- Y (fix/123)

More complex cases are possible, such as multiple merge to complete a fix :

- A - B - C - D - E - F - G - H (master) 
               \     /       /
                X - Y ----- Z (fix/123)

But we don't allow merge into a fix branch, so we shall never have something like this :

- A - B - C - D - E - F - G (master) 
               \   \     /
                X - Y - Z (fix/123)

To achieve this, we can cherry-pick or rebase the fix branch :

1) cherry-pick (typicaly How do I backport a commit in git?) :

git checkout -b fix/123-stable stable
git cherry-pick X Y

This seems easy, but it is not when dealing with real life examples ; there is always a risk to forget some commits, or to pick wrong ones!

2) rebase --onto (https://www.kernel.org/pub/software/scm/git/docs/git-rebase.html) :

2.a) the "not working" way :

git rebase --onto stable master fix/123

This does nothing since fix/123 has already been merged to master! 2.b) the "not far better than cherry-pick" way :

git rebase --onto stable D fix/123

This is still kind of risky because you need to take the SHA of D (and NOT X for instance).

2.c) the "use a temporary starting ref" way :

git tag begin D
git rebase --onto stable begin fix/123
git tag -d begin

This improve the previous situation, as the tag make it easier to do it or picture it in a graphical tool, but it is still lot of manual work.

3.d) the "reset hard master before the merge" (to the first branching point) Hum, seems hard to describe and to do.

So, what I am looking for is a git portable (no bash/grep/cut/sed implied) way to either;

1) list all commits made on a branch already merged back into "master" (here X and Y, and also Z in the "multi-merged" case) to cherry-pick them easily

2) get the commit of the first branch point of a branch already merged back into "master"

2.a) this cannot be done by the "git merge-base" command because the merge is already done (even multiple time)

2.b) I've found here Finding a branch point with Git? the following bash command I tweaked a bit:

git rev-list --boundary --date-order --reverse fix/123..master | grep -m 1 - | cut -c2-

but his is not a git easy nor portable command (ie not working without Bash or Cygwin tools)

like image 863
SRombauts Avatar asked May 22 '13 11:05

SRombauts


People also ask

What is rebasing merging cherry picking?

Both do very similar things; the main conceptual difference is (in simplified terms) that: rebase moves commits from the current branch to another branch. cherry-pick copies commits from another branch to the current branch.

How do you cherry pick a merge commit?

With the cherry-pick command, Git lets you incorporate selected individual commits from any branch into your current Git HEAD branch. When performing a git merge or git rebase , all the commits from a branch are combined. The cherry-pick command allows you to select individual commits for integration.

Can I reuse a merged branch?

You can always reuse that branch if you'd like or remove it. In Bitbucket Cloud we wont' remove the branch after a pull request is merged unless you use the "Close branch" checkbox during pull request creation.

How do you cherry pick commits to another branch?

In the list of branches, click the branch that has the commit that you want to cherry-pick. Click History. Drag the commit that you want to cherry-pick to the Current Branch menu and drop the commit on the branch that you want to copy the commit to.


2 Answers

For the record, here are the two solutions I am finally using based on the answer of Craig Otis here pointing at the answer of lindes on "Finding a branch point with Git", using Bash aliases the ".gitconfig" file (tested under Linux Ubuntu 12.10)

Initial state, where the branch "fix/123" has already been merged back into "master":

        I--J (stable)
       /
- A - B - C - D - E - F - G  (master) 
               \     /
                X - Y (fix/123)

1) To rebase the branch "fix/123" starting from "master" onto "stable" (this is a generic answer for most people reading this):

Add the following Bash aliases into your ".gitconfig" file:

[alias]
    oldest-ancestor = !bash -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -
    rebase-onto     = !bash -c 'git rebase --onto $1 `git oldest-ancestor $2 $3` $3' -

Then use the command line:

git rebase-onto stable master fix/123

And here you are:

          I--J (stable)
         /    \
        /      X'- Y' (fix/123)
       /
- A - B - C - D - E - F - G  (master) 
               \     /
                X - Y

2) To rebase the branch "fix/123" starting from "master", creating a new branch "fix/123-stable" onto "stable" (this is the more specific answer I am going to use).

Add the following Bash aliases into your ".gitconfig" file:

[alias]
    oldest-ancestor = !bash -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -
    rebase-onto     = !bash -c 'git branch $4 $2 && git rebase --onto $3 `git oldest-ancestor $1 $4` $4' -

Then use the command line:

git rebase-onto master fix/123 stable fix/123-stable

And here you are:

          I--J (stable)
         /    \
        /      X'- Y' (fix/123-stable)
       /
- A - B - C - D - E - F - G  (master) 
               \     /
                X - Y (fix/123)
like image 55
SRombauts Avatar answered Oct 08 '22 20:10

SRombauts


I'm fairly new to Git, so please excuse any misunderstandings I may have made in your situation.

Using this code snippet:

diff -u <(git rev-list --first-parent fix/123) <(git rev-list --first-parent master) | sed -ne 's/^ //p' | head -1

From this answer: @lindes answer to "Finding a branch point with Git?"

If you have a tree like this:

Initial Tree

Then the result of the above command with some-fix in place of fix/123 will be:

f1efa4a9c029281a22d4fa8dd6607c523e7191f5

Which is the commit at which your initial fix branch was created.

You can then also run a quick merge-base to determine where some-fix ends:

$ git merge-base master some-fix
1b9ebb7157a958e9adc3b8eda9bf4175cd821c4b

And then, using a cherry-pick of the revision range, you can pull those changes in:

$ git cherry-pick f1efa4a..1b9ebb7

And you end up with:

Final Tree

Which contains the additional commits from your initial fix branch. You would want to perform the additional checkout -b to create your fix/123-stable branch, instead of just tacking them on to stable, but the method should be the same.

Note also that the answer I referenced mentions installing that long, tedious diff command as an alias called oldest-ancestor, which should save you a ton of time.

like image 34
Craig Otis Avatar answered Oct 08 '22 21:10

Craig Otis