Tutorial :How to split last commit into two in Git



Question:

I have two working branches, master and forum and I've just made some modifications in forum branch, that I'd like to cherry-pick into master. But unfortunately, the commit I want to cherry-pick also contains some modifications that I don't want.

The solution would probably be to somehow delete the wrong commit and replace it with two separate commits, one with changes I want to pick in master, and others that doesn't belong there.

I've tried doing

git reset --hard HEAD^  

which deleted all changes, so I had to go back with

git reset ORIG_HEAD  

So my question is, what is the best way to split last commit into two separate commits?


Solution:1

You should use the index. After doing a mixed reset ("git reset HEAD^"), add the first set of changes into the index, then commit them. Then commit the rest.

You can use "git add" to put all changes made in a file to the index. If you don't want to stage every modification made in a file, only some of them, you can use "git add -p".

Let's see an example. Let's suppose I had a file called myfile, which contains the following text:

something  something else  something again  

I modified it in my last commit so that now it looks like this:

1  something  something else  something again  2  

Now I decide that I want to split it into two, and I want the insertion of the first line to be in the first commit, and the insertion of the last line to be in the second commit.

First I go back to HEAD's parent, but I want to keep the modifications in file system, so I use "git reset" without argument (which will do a so-called "mixed" reset):

$ git reset HEAD^  myfile: locally modified  $ cat myfile  1  something  something else  something again  2  

Now I use "git add -p" to add the changes I want to commit to the index (=I stage them). "git add -p" is an interactive tool that asks you about what changes to the file should it add to the index.

$ git add -p myfile  diff --git a/myfile b/myfile  index 93db4cb..2f113ce 100644  --- a/myfile  +++ b/myfile  @@ -1,3 +1,5 @@  +1   something   something else   something again  +2  Stage this hunk [y,n,a,d,/,s,e,?]? s    # split this section into two!  Split into 2 hunks.  @@ -1,3 +1,4 @@  +1   something   something else   something again  Stage this hunk [y,n,a,d,/,j,J,g,e,?]? y  # yes, I want to stage this  @@ -1,3 +2,4 @@   something   something else   something again  +2  Stage this hunk [y,n,a,d,/,K,g,e,?]? n   # no, I don't want to stage this  

Then I commit this first change:

$ git commit -m "Added first line"  [master cef3d4e] Added first line   1 files changed, 1 insertions(+), 0 deletions(-)  

Now I can commit all the other changes (namely the numeral "2" put in the last line):

$ git commit -am "Added last line"  [master 5e284e6] Added last line   1 files changed, 1 insertions(+), 0 deletions(-)  

Let's check the log to see what commits we have:

$ git log -p -n2 | cat  Commit 5e284e652f5e05a47ad8883d9f59ed9817be59d8  Author: ...  Date: ...        Added last line    Diff --git a/myfile b/myfile  Index f9e1a67..2f113ce 100644  --- a/myfile  +++ b/myfile  @@ -2,3 +2,4 @@   something   something else   something again  +2    Commit cef3d4e0298dd5d279a911440bb72d39410e7898  Author: ...  Date: ...        Added first line    Diff --git a/myfile b/myfile  Index 93db4cb..f9e1a67 100644  --- a/myfile  +++ b/myfile  @@ -1,3 +1,4 @@  +1   something   something else   something again  


Solution:2

Goals:

  • I want to split a past commit (splitme) into two.
  • I want to maintain the commit message.

Plan:

  1. rebase interactive from one before splitme.
  2. edit splitme.
  3. Reset the files to split into a second commit.
  4. Amend commit, maintaining message, modify as necessary.
  5. Add back the files split out from the first commit.
  6. Commit with a new message.
  7. Continue rebase.

The rebase steps (1 & 7) can be skipped if the splitme is the most recent commit.

git rebase -i splitme^  # mark splitme commit with 'e'  git reset HEAD^ -- $files  git commit --amend  git add $files  git commit -m "commit with just some files"  git rebase --continue  

If I wanted the split files to be committed first, I'd then rebase -i again and switch the order

git rebase -i splitme^  # swap order of splitme and 'just some files'  


Solution:3

To change the current commit into two commits, you can do something like the following.

Either:

git reset --soft HEAD^  

This undoes the last commit but leaves everything staged. You can then unstage certain files:

git reset -- file.file  

Optionally restage parts of those files:

git add -p file.file  

Make a new first commit:

git commit  

The stage and commit the rest of the changes in a second commit:

git commit -a  

Or:

Undo and unstage all of the changes from the last commit:

git reset HEAD^  

Selectively stage the first round of changes:

git add -p  

Commit:

git commit  

Commit the rest of the changes:

git commit -a  

(In either step, if you undid a commit that added a brand new file and want to add this to the second commit you'll have to manually add it as commit -a only stages changes to already tracked files.)


Solution:4

Run git gui, select the "Amend last commit" radio button, and unstage (Commit > Unstage From Commit, or Ctrl-U) changes that you do not want to go into first commit. I think that's the easiest way to go about it.

Another thing you could do is cherry-pick the change without committing (git cherry-pick -n) and then either manually or with git gui select desired changes before committing.


Solution:5

git reset HEAD^  

the --hard is what's killing your changes.


Solution:6

I'm surprised nobody suggested git cherry-pick -n forum. This will stage the changes from the latest forum commit but not commit them - you can then reset away the changes you don't need and commit what you want to keep.


Solution:7

The double-revert-squash method

  1. Make another commit that removes the unwanted changes. (If it's per file, this is really easy: git checkout HEAD~1 -- files with unwanted changes and git commit. If not, files with mixed changes can be partially staged git reset file and git add -p file as an intermediate step.) Call this the revert.
  2. git revert HEAD â€" Make yet another commit, that adds back the unwanted changes. This is the double-revert
  3. Of the 2 commits you now made, squash the first onto the commit to split (git rebase -i HEAD~3). This commit now becomes free of the unwanted changes, for those are in the second commit.

Benefits

  • Preserves the commit message
  • Works even if the commit to split is not the last one. It only requires that the unwanted changes do not conflict with later commits


Solution:8

Since you're cherry-picking, you can:

  1. cherry-pick it with --no-commit option added.
  2. reset and use add --patch, add --edit or just add to stage what you want to keep.
  3. commit the staged changes.
    • To re-use original commit message, you can add --reuse-message=<old-commit-ref> or --reedit-message=<old-commit-ref> options to the commit command.
  4. Blow away unstaged changes with reset --hard.

Another way, preserving or editing the original commit message:

  1. cherry-pick the original commit as normal.
  2. Reverse the changes you don't want and use add to stage the reversal.
    • This step would be easy if you are removing what you added, but a bit tricky if you're adding what you removed or reversing a change.
  3. commit --amend to effect the reversal on the cherry-picked commit.
    • You'll get the same commit message again, which you can keep or revise as necessary.

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »