Let\'s say I have:
A - B - C - D - E - F master
\\
\\- G - H new feature branch
Now I realize that commits B an
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.
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.
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