How can I move some commits of a feature from an existing branch into a feature branch, and effectively remove them from the current branch - as if the feature was developed in a separate feature branch?
Background: the branch develop
contains commits to two features A (say, 50 commits) and B (10 commits) wildly mixed, since they originally should have been in the same release. Now we are delaying feature B. Thus, we want to have only feature A in the develop
branch, and a new featureB
branch that contains the commits of A or A and B, such that if we merge both branches, we get the current position of the develop
branch.
One way to do that would be to create the featureB
branch from the current position of the develop
branch and then reversely apply the commits from A to develop
. But then the merge of the new develop
and featureB
would just be A.
Another way would be to rename the current develop
into featureB
and cherry pick all commits of A into a new develop
branch. But that would effectively modify the history of develop
, which is troublesome.
What is a better way to do that?
If your develop
branch has been published and you don't want to rewrite its history (which would be the easiest way), then you could indeed revert your changes in develop
, start a new featureB
branch off rebased B
changes on top of develop
. Something along the line:
#given history (top - newest)
#shaA3 <-- develop, HEAD
#shaB2
#shaA2
#shaB1
#shaA1
revert:
git revert B2
git revert B1
now the history contains:
#revert of shaB1 <-- develop, HEAD
#revert of shaB2
#shaA3
#shaB2
#shaA2
#shaB1
#shaA1
create featureB
and re-play reverted commits anew:
git checkout -b featureB
git rebase -i --onto develop shaB1~1 featureB
Comment out all the commits except for those belonging to feature B (shaB1
and shaB2
in our case) and complete the rebase. At this point you should have the history:
#shaB2' <-- featureB, HEAD
#shaB1'
#revert of shaB1 <-- develop
#revert of shaB2
#shaA3
#shaB2
#shaA2
#shaB1
#shaA1
To double-check that everything went well, you can do git diff shaA3
- should be empty, git diff develop
- should contain all of the desired B changes.
P.S. You can of course use cherry-pick or revert of reverts to replay the b changes into branchB
, instead of the rebase interactive, e.g. when staying on develop:
git checkout -b branchB
git revert <revert of shaB1>
git revert <revert of shaB2>
Will give you:
#revert of revert of shaB2 = shaB2' <-- featureB, HEAD
#revert of revert of shaB1 = shaB1'
#revert of shaB1 <-- develop
#revert of shaB2
#shaA3
#shaB2
#shaA2
#shaB1
#shaA1
By far the cleanest method if you can manage it at all is to just write the correct histories and switch refs. Calling the base of the history you want to split X
,
git checkout -b new-develop X
git cherry-pick [all the feature_A commits]
# repeat the cherry-pick as needed or convenient if there's too many
git checkout -b new-featureB X
git cherry-pick [all the feature_B commits]
# ...
then swap names around with git branch -m
, force-push, and have everyone refetch and rebase any unpublished work as necessary.
Every other option is going to leave you with a really messy history, and there's no reason to inflict that on all of posterity if it's at all reasonable to avoid it. Do avoid it if for any reason communication is a problem.
If you really can't do that, then see @MykolaGurov's nicely detailed answer.
( branch
→ checkout -b
. . . )
Create branch featureB
from current state of develop. Commit code to branch develop
for any further development of featureA. Commit code to branch featureB
for development of featureB. Regularly rebase branch featureB
against branch develop
so that it has the changes which are being added for development of featureA.
If you want to avoid modifying the published history of branch develop
, as in @thill's answer, and also want to avoid reverting the reverted commits, as in @Mykola Gorov's answer, you can also
- Create branch
featureB
fromdevelop
- Revert the commits of feature
B
into branchdevelop
. It might be sensible to do this in a single commitrevert B
, since this is one operation in the history. - Merge branch
develop
intofeatureB
with strategyours
. This does not change any files in branchfeatureB
, but marks the reverted commit from 2. as already merged intofeatureB
. Thus, if you later merge the feature branchfeatureB
back intodevelop
, the result will not contain commitrevert B
anymore.
When merging featureB
back into develop
you might want to have featureB
as the first parent of the commit. (E.g. you merge develop
into featureB
and then set develop
to featureB
, not the other way around.) I suppose that way the reversion won't confuse blame etc. anymore. (?)
来源:https://stackoverflow.com/questions/29350859/git-how-to-separate-out-a-feature-branch-after-the-fact