简体   繁体   中英

Git merge only commits within branch

Consider this tree below. Excuse the crappy diagram, but I think you get the idea.

I have a MASTER branch and a MY BRANCH . I create a PATCH branch from MASTER and make my fixes on this branch. I then merge the fixes into both MASTER and MY BRANCH . When I do this, MY BRANCH also gets commits C & D along with it.

MASTER----A----B----C----D-------------MERGE
                \         \             /
                 \       PATCH----E----F
                  \                     \
             MY BRANCH----G----H-------MERGE

How can I merge only the commits in the patch into both MASTER and MY BRANCH ?

As RomainValeri noted, the graph can be drawn better. I'll borrow his variant to start, but draw what we have before you try to do any merging:

              E---F <<< patch
             /
A---B---C---D <<< master
     \
      G---H <<< my-branch

The name patch refers to existing commit F and the names master and my-branch refer to existing commit H .

The problem you have occurs because git merge works based on the commit graph , not on the branch names . The names find commits, which is OK as far as that goes, but if you run git merge <commit F> , the result will include commit F , which will include commit E , which will include commit D , which will include commit C , which will include commit B , and so on.

That is, any successful merge of commit F automatically includes every commit leading up to and including F itself .

You can use git cherry-pick to copy individual commits. Remember, a commit is a snapshot , a complete set of all files. But every commit has a parent commit too. 1 If you compare the snapshot in commit E with the snapshot in its parent commit D , you'll see what you changed in commit E . If you compare the snapshot in commit F with its parent in commit E , you'll see what you changed in commit F . A cherry-pick essentially allows you to "replay" these changes elsewhere.

This leads to the recommendation that people will often make, to use cherry-pick here. If you make a new branch named patch2 , starting at commit H , and copy E and F to new and improved commits E' and F' , where E' and F' come after commit H , you get:

              E---F   <-- patch
             /
A---B---C---D   <-- master
     \
      G---H   <-- my-branch
           \
            E'-F'  <-- patch2

You can now merge the original patch into master and the new-and-different (supposedly improved) patch2 branch into my-branch .

There is a better way! Well, often , anyway. Let's step back for a moment and ask: Why did you write commits E and F in the first place?

Chances are that it is to fix a bug that is in commit A , or commit B , or maybe both. Or maybe it adds a feature that is just lacking in commit B . Whatever the case, you believe that it can be applied without commits C and D .

What you should do now is prove this . Make a new branch patch2 but don't point it at existing commit H . Start it instead at commit B . Then copy E and F to your new-and-improved E' and F' , giving:

          E--F   <-- patch
         /
     C--D   <-- master
    /
A--B--E'-F'  <-- patch2
    \
     G--H   <-- my-branch

Note that all the existing commits are still there, exactly the same. (You have not merged anything anywhere yet.) But if patch2 really works, we can now throw away the branch named patch , abandoning commits EF :

          E--F   [abandoned]
         /
     C--D   <-- master
    /
A--B--E'-F'  <-- patch2
    \
     G--H   <-- my-branch

Now that we've done that, let's stop drawing them, and rename existing branch patch2 to patch :

     C--D   <-- master
    /
A--B--E'-F'  <-- patch
    \
     G--H   <-- my-branch

The copied (cherry-picked or rebased ) commits E'-F' are now in a position in which we can run git checkout master; git merge patch git checkout master; git merge patch and git checkout my-branch; git merge patch git checkout my-branch; git merge patch . When we do, we'll get two merge commits:

     C--D--M1   <-- master
    /     /
A--B--E'-F'  <-- patch
    \     \
     G--H--M2   <-- my-branch

These merges behave the way you like: they bring only the patch into the branch, not any other commits unrelated to the patch.

This leads to a general rule about making patches

If you're going to fix a bug or add a shared feature, find the earliest commit in your graph where the bug or feature can go. In this case, that was just after commit B . (It might be possible to put it just after commit A , and if so, you can do that instead. If the bug or feature requires part of commit B , though, commit B is the furthest back you can go.)

The reason for doing this is that the resulting patch can be cleanly merged to any commit that is "downstream" of that point. In this case, you wanted to merge the patch into commits D , on master , downstream of B , and into H , on my-branch , downstream of B . Since patch , at commit F' , is downstream of B , merging patch into any branch will bring in commit B . But commit B is the first place at which branch patch is useful . Any branch whose tip isn't downstream of B cannot use the patch at all! So by placing the patch itself immediately after the first place it could possibly be used , every other branch that can use the patch at all, can use the patch easily and cleanly.

使用rebasecherry-pick手动将EF应用到MYBRANCH或仅以EF开始正确创建PATCH分支。

You can use a range to designate which commits you want on the destination branch :

git checkout my-branch
git cherry-pick master..patch

where master..patch means :
everything on patch MINUS everything already known on master


As a sidenote, suggestion for a better tree schema

              E---F <<< patch
             /     \
A---B---C---D-------I <<< master
     \
      G---H <<< my-branch

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM