-->

Git: how to separate out a feature branch after th

2019-06-06 22:03发布

问题:

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?

回答1:

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


回答2:

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.


( branchcheckout -b . . . )



回答3:

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.



回答4:

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

  1. Create branch featureB from develop
  2. Revert the commits of feature B into branch develop. It might be sensible to do this in a single commit revert B, since this is one operation in the history.
  3. Merge branch develop into featureB with strategy ours. This does not change any files in branch featureB, but marks the reverted commit from 2. as already merged into featureB. Thus, if you later merge the feature branch featureB back into develop, the result will not contain commit revert 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. (?)