views:

1235

answers:

5

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?

+5  A: 

Try running git gui, selecting Amend last commit radio button and unstage 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.

Michael Krelin - hacker
+3  A: 
git reset HEAD^

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

semanticart
A: 

To achieve your original goal, avoiding the question of splitting a commit, you can do things manually.

  1. Get the desired content, placing it into a temporary folder.
  2. Use something like WinMerge to compare the destination folder with the temporary folder.
  3. Copy just the changes you want.
  4. Commit those changes.

To perform the actual commit split, you could do a variation on the above.

  1. Get the content previous to your checkin, into the main folder.
  2. Check it in, so it is newer than the commit you want to split.
  3. Follow the steps from the section above to get one portion of the split.
  4. Commit
  5. Follow the steps from the section above to finish off the split.
  6. Commit

This process should work with most version control systems.

John Fisher
+18  A: 

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, 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
hcs42
+1  A: 

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.)

Charles Bailey