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

吃可爱长大的小学妹 提交于 2019-12-18 03:45:53

问题


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)


回答1:


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)



回答2:


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:

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:

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.



来源:https://stackoverflow.com/questions/16690187/how-to-git-backport-rebase-cherry-pick-an-already-merged-branch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!