3

My team has been working in a prototype branch off of master. I now want to take that work, slice it up into different "feature branches", and merge them individually into master. I see a couple ways to do this, neither of which I really like:

1 - Create a new branch, Feature_1, off of master. Manually copy the code from the Prototype to Feature_1. This means I have to keep track of what I've copied when I go to make Feature_N and I lose history.

2 - Create a new branch, Feature_1, off of Prototype. Somehow revert the code that is not part of the first feature in Feature_1. This avoids lying to git (and keeps history), but it feels like Feature_N will be a mess to merge because I will have told master that the changes were reverted when I pushed Feature_1.

Am I missing a nicer way to do this?

JoeB
  • 2,743
  • 6
  • 38
  • 51

2 Answers2

7

@Michael's answer is the good stuff if your commits are single-feature commits that don't share dependencies with commits for any other feature. If you've mixed work on the two features in any commit, though, you'll want interactive rebase. It allows you to arbitrarily redistribute change hunks and commit boundaries, and it does keep track of which hunks haven't yet been committed to the current branch.

If the feature changes are just sometimes combined into commits and there are no cross-feature dependencies, to make life easy my first try would be to git rebase -i master prototype, split the commits with mixed hunks into two commits, one for each, and then finish off with the cherry-picks as in Michael's answer. Given

A1-B2-C12-D2-E1-F12    prototype

where the digits signify which feature(s) the commit contains code for, for `git rebase -i master prototype you'd edit commits C12 and F12,

pick A1
pick B2
edit C12
pick D2
pick E1
edit F12

(using each commit's hash instead of its illustrative tag here).

Rebase will stop after committing C12, and you can git reset HEAD~ then git add --patch to apply all the feature-1 hunks, git commit to create commit C1 where C12 was, then git commit -a to apply all the remaining hunks and create commit C2 following it. You'll end up with

A1-B1-C1-C2-D2-E1-F1-F2

and you can then git checkout -b feature1 master; git cherry-pick A1 B1 C1 E1 F1 and similarly for feature2.

In more complicated situations this method still works with only very minor changes. Interactive rebase is much better than the above might lead you to believe, but by far the best way to find out about that is to sit down with the manpage while you get in there and scramble some hunks for fun. Do that, and it may soon get to the point where doing this as a prepublication ritual is often enough more convenient than trying to keep your actual workflow publishable at every little step.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • if you do `git reset HEAD~` then you need to do a normal `git commit` (without the `--amend`) afterwards to create a new commit `C1`. Otherwise `git commit --amend` would edit (amend) the commit before (`B2` in this example) and you would end up with `...-B12-C2-...`. Alternatively, you can reset just single files with `git reset HEAD~ /path/to/file` which will leave the rest of the commit in place, so you can then modify it with `git commit --amend`. – iliis Feb 03 '21 at 12:00
  • 1
    @iliis right you are, it should either be `git read-tree @~` instead of `git reset @~` or plain `git commit` instead of `git commit --amend`. Thanks for pointing this out, fixed. – jthill Feb 03 '21 at 19:00
3

Create two branches feature_1 and feature_2 off of master and cherry pick the commits from prototype in the regarding feature branch:

git checkout -b feature_1 master
git cherry-pick <commit>
git cherry-pick <commit>
…

git checkout -b feature_2 master
git cherry feature_1 prototype | grep "^+" | cut -c3- | xargs git cherry-pick

The last line cherry picks all commits from prototype which are not in feature_1 into the current branch, i.e. feature_2.

When you run into conflicts, use git status for hints how to continue.

See git-cherry-pick for further documentation.

Michael
  • 1,502
  • 19
  • 29
  • We had actually come up with cherry-pick when discussing here as well, I'll try this. – JoeB Oct 30 '14 at 17:27
  • Is there a way to do this if none of the changes have been committed yet? – Michael Feb 06 '18 at 20:31
  • Create a new branch, use `git add --patch` to add the changes you want to have in this branch (+ `git add` for new files), commit, stash the remaining changes, checkout a new branch based on master (`git checkout -b feature_2 master`), add and commit your changes. – Michael Feb 07 '18 at 09:40