0

I made a few dozen edits in a new git branch I called "performance". Now I got order to split those edits so several branches called "performance-frontend", "performance-logging" etc.

I thought of: create a new branch without altering any checked out files, add the appropriate files, commit, repeat.

Can this be done? Ot is there another (established) way?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Zsolt Szilagyi
  • 4,741
  • 4
  • 28
  • 44
  • 1
    "edits" or "commits"? – crashmstr Oct 05 '17 at 13:30
  • If what you call "edits" are commits then one way is to use [`git cherry-pick`](https://git-scm.com/docs/git-cherry-pick). – axiac Oct 05 '17 at 13:32
  • So far nothing is commited to keep my options of splitted commiting open. Of course I can commit everything into current directory if that's the way how it works. – Zsolt Szilagyi Oct 05 '17 at 13:34
  • If you have uncommitted changes, create a branch, stage some of the changes and commit, checkout performance again, repeat. – crashmstr Oct 05 '17 at 14:59

3 Answers3

2

As far as I understand you were in a similar situation as I have right now: A mixture of changes have been committed to the same branch, but should be pulled apart into separate branches.

Current state:
... -- A (master) -- C1 -- B1 -- B2 -- C2 -- B3 -- C3 -- B4 (devel)

Wanted state:
... -- A (master) -- B1’ -- B2’ -- B3’ -- B4’ (branch-B)
        \
         `---------- C1’ -- C2’ -- C3’ (branch-C)

In my case, this state was achieved after messy experimentation by performing a soft-reset to master, and committing changes line by line with git gui, flagging each change with a commit message A or B. So let’s assume such preparation has already happened, and the commits An, Bn are already cleanly separated.

Cherry-picking manually could end up being error prone, as for long sequences of commits it is easy to miss one. So instead we use git rebase -i on a temporary branch, to be on the safe side.

>> git checkout devel
>> git checkout -b tmp

... -- A (master) -- C1 -- B1 -- B2 -- C2 -- B3 -- C3 -- B4 (devel, tmp, HEAD)

>> git rebase -i master

This will open a rebase-task file. It defaults to

pick 552fe03 C1
pick cbf327a B1
pick bd1ca26 B2
pick 95320e6 C2
pick 7f21156 B3
pick 910a6fe C3
pick bfda579 B4

What this does is essentially “reset branch to master, then perform commands listed in rebase file”. With the default setting, it essentially rebuilds the branch, leaving everything unchanged.

1. Reordering commits.

So instead we reorder the commits.

pick cbf327a B1
pick bd1ca26 B2
pick 7f21156 B3
pick bfda579 B4
pick 552fe03 C1
pick 95320e6 C2
pick 910a6fe C3

Depending on what changes have been made, merge conflicts can occur, which have to be resolved before rebase can be continued with git rebase --continue.

It can be useful to move only one commit at a time, repeating git rebase -i master several times; Otherwise you risk losing work, if you find yourself having to abort the rebase.

The result will be:

... -- A (master) -- C1 -- B1 -- B2 -- C2 -- B3 -- C3 -- B4 (devel)
        \
         `-- B1 -- B2 -- B3’ -- B4’ -- C1’ -- C2’ -- C3’ (tmp, HEAD)

2. Create the branches.

We can now create the new branches,

>> git branch branch-C tmp
>> git branch branch-B <hash-of-B4’>

Giving a state

... -- A (master) -- -- B1 -- B2 -- C1 -- B3 -- C2 -- C3 -- B4 (devel)
        \
         `-- B1’ -- B2’ -- B3’ -- B4’ (branch-B) -- C1’ -- C2’ -- C3’ (tmp, branch-C, HEAD)
         

3. Pull them apart with another rebase.

As described in this answer to Split a git branch into two branches?, we can now use a rebase command of the form

>> git rebase --onto NEW_PARENT OLD_PARENT BRANCH_TO_MOVE

in this case:

>> git rebase --onto master branch-B branch-C 

... -- A (master) -- B1 -- B2 -- C1 -- B3 -- C2 -- C3 -- B4 (devel)
       |
       |`-- B1’ -- B2’ -- B3’ -- B4’ (branch-B) -- C1’ -- C2’ -- C3’ (tmp)
       |
        `-- C1’’-- C2’’-- C3’’ (branch-C, HEAD)
        

If all has worked, all that is left is to force-remove the deprecated/temporary branches (normal delete with -d doesn’t work here).

>> git branch -D devel
>> git branch -D tmp

... -- A (master) -- B1’ -- B2’ -- B3’ -- B4’ (branch-B)
        \
         `---------- C1’’-- C2’’-- C3’’ (branch-C, HEAD)
         

Without temporary branches.

Of course you don’t have to use temporary branches. Then the commands would be

>> git checkout devel
>> git rebase -i master     # for reordering
>> git branch -m branch-C   # rename ‘devel’ to ‘branch-C’
>> git branch branch-B <hash-of-B4’>
>> git rebase --onto master branch-B branch-C

But rebases can fail and it is easier to recover when using branches, then having to reconstruct the correct state from git reflog.

kdb
  • 4,098
  • 26
  • 49
1

You could do git add/git commit in the current branch and create multiple commits (each commit should affect the set of files that you want to consider in a single branch). Then create the branches and cherry-pick as @axiac suggests. Or create the branches, checkout each branch and copy the relevant files: this is harder unless you use worktrees, since when you check out the new branch, the changed files in the performance branch are not going to be necessarily visible

NickD
  • 5,937
  • 1
  • 21
  • 38
1

Well, you can do it in a fev different ways. But the most easy-ish imho is:

Reset your branch back to the commit where you started out(no --hard!). After this, you should see all the changes you made in git status.

Now after this use your favourite git gui, or git add -i, to selectively stage only the particular changes you want, and kinda re-commit them as you like.

Or you can create the branches you want, and cherry-pick the commits from your original branch( if you commited them so it's logically separated as you want) where you want, just keep their order, to avoid unnecessary conflicts.

Hell, you can even copy out your changes, check out your starting point, and copy them back, then commit them. (this one is not so gitbased :D) and there are other ways too

So as usual, with git, you can do it in a lot of different ways.

MrKekson
  • 720
  • 6
  • 18