git: how to move a branch's root two commits back

后端 未结 3 1471
闹比i
闹比i 2021-01-07 01:44

Let\'s say I have:

A - B - C - D - E - F  master
            \\ 
             \\- G - H  new feature branch

Now I realize that commits B an

相关标签:
3条回答
  • 2021-01-07 02:26

    You can do this with git rebase.

    First, let's rebase some commits from master:

    $ git checkout master
    $ git rebase --onto A C
    

    This will move all commits in the range from C to master (but not including C itself) onto the commit A.

    Now rebase feature but throw out commit D:

    $ git checkout feature
    $ git rebase --onto C D
    

    Similar to the previous command, this will move commits in the range from D to feature (not including D itself) onto C.

    0 讨论(0)
  • 2021-01-07 02:29

    Edmundo's answer is right (and upvoted), but it's worth pointing out a few extra items.

    First, your question talks about "moving the branch's root"—but this is Git; branches don't have roots, not the way you are thinking anyway. Let's look at your graph drawing:

    A - B - C - D - E - F  master
                \ 
                 \- G - H  new feature branch
    

    I think that you, like me when I first started using Git, would like to think of commits A-B-C-D-E-F as being on the master branch, and commits G-H as being on the feature branch. But that's not how Git works. Let's re-draw this, without changing any of the commit links:

               E--F   <-- master
              /
    A--B--C--D
              \
               G--H   <-- feature
    

    This should make it clearer that, as far as Git is concerned, commits A-B-C-D are on both branches. There is only one root. That's commit A: it's a root because it has no parent commit, no backwards link in the chain from commit to parent.

    Second, no matter how you go about this, you wind up having to copy some commits. The reason is that each commit's parent ID is part of that commit's identity. The "true name" of any commit is its hash ID, and the hash ID is built by reading the complete contents of the commit: source tree, commit message, author name and date, etc., but always including the parent ID too.1 You want your final graph to resemble:

      D--E--F   <-- master
     /
    A
     \ 
      B--C--G--H   <-- feature
    

    but the existing D links (or points) back to the existing C, not to A, and the existing G points back to the existing D.

    This is why cherry-pick works: git cherry-pick essentially copies a commit. The new copy "does the same thing" as the original, but has something different, even if it's as simple as "my parent is ...". (Usually it also has a different attached tree object as well, it's just that the change it makes, when compared to its new parent, is the same as the change the original makes when the original is compared to the original's parent). What this means is that you can't actually get what you want, just what you need:

      D'-E'-F'  <-- master
     /
    A
     \ 
      B--C--G'-H'  <-- feature
    

    where the little tick mark indicates that the result is a copy of the original.

    What happens to the originals? The answer is: they're still there, in the repository, in case you need them. The full picture is more like this:

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

    While git cherry-pick works, what git rebase—especially git rebase -i—does is to do these copies in a fancy automated fashion, with a final step to move the branch name, abandoning the original commits. So git rebase -i is sometimes an easier way to go about it.

    If you run git rebase -i you see all those pick commands, and those literally run git cherry-pick: it really is doing a series of cherry-picks. If you do these yourself, it may be clearer what is going on, and you have a lot more control over when the branch labels are moved-around.


    1For merge commits, this is parents, plural. All parents participate in the hash.

    0 讨论(0)
  • 2021-01-07 02:46

    To fix master:

    git checkout A
    git cherry-pick master~3..master # apply the changes we actually want to keep on master
    git branch -f master # reposition master on new position
    git checkout master
    

    To drop commit D from the feature branch:

    git checkout feature-branch~3 # or git checkout C
    git cherry-pick feature-branch~2..feature-branch # apply the last 2 revisions
    git branch -f feature-branch
    git checkout feature-branch
    
    0 讨论(0)
提交回复
热议问题