Reputation: 87270
I have two branches, master
and forum
, and I've just made some modifications in forum
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
, the other with the remaining changes.
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 the last commit into two separate commits?
Upvotes: 291
Views: 93217
Reputation: 4809
$splitme
into two.$splitme
, marking $splitme
as edit
.The rebase steps (1 and 7) can be skipped if the $splitme
is the most recent commit.
git rebase -i $splitme^ # mark $splitme as edit
git reset HEAD^ -- $files
git commit --amend
git add $files
git commit
git rebase --continue
If I wanted to swap the files in the first commit with the files in the second commit, I would then rebase interactive again, swapping their commit line.
Upvotes: 101
Reputation: 14188
You can use git rebase -i <commit>
, where <commit>
is the latest commit you want to keep as-is. Add a break
at each point where you would like to insert a new split-out commit. Then at each break, use git checkout -p <commit containing parts you want>
to pull in the parts you want to split out, and commit them. Then git rebase --continue
to remove those parts from later commits.
For the simple case of splitting the most recent commit, this looks like:
$ git rebase -i HEAD^
# add 'break' at the start
$ git checkout -p master # or whatever your branch is called
# choose the parts you want to split out
$ git commit
# commit the newly-split-out parts
$ git rebase --continue
# rebase the remaining parts of the change onto the split-out parts
This assumes you want the later commit to retain the original commit message; that's the situation I usually find myself in (factoring out some preparatory change).
Upvotes: 1
Reputation: 596
This might be another solution targeted for cases where there is a huge commit and a small amount of files needs to be moved into a new commit. This will work if a set of <path>
files are to be extracted out of the last commit at HEAD and all moved to a new commit. If multiple commits are needed the other solutions can be used.
First make patches into the staged and unstaged areas that would contain the changes to revert the code to before modification and after modification respectively:
git reset HEAD^ <path>
$ git status
On branch <your-branch>
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: <path>
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: <path>
To understand what's gonna happen (arrow and comments are not part of command):
git diff --cached -> show staged changes to revert <path> to before HEAD
git diff -> show unstaged changes to add current <path> changes
Revert <path>
changes in last commit:
git commit --amend -> reverts changes on HEAD by amending with staged changes
Create new commit with <path>
changes:
git commit -a -m "New Commit" -> adds new commit with unstaged changes
This has the effect of creating a new commit containing the changes extracted out of the last commit.
Upvotes: 1
Reputation: 10131
Since you're cherry-picking, you can:
cherry-pick
it with --no-commit
option added.reset
and use add --patch
, add --edit
or just add
to stage what you want to keep.commit
the staged changes.
--reuse-message=<old-commit-ref>
or --reedit-message=<old-commit-ref>
options to the commit
command.reset --hard
.Another way, preserving or editing the original commit message:
cherry-pick
the original commit as normal.add
to stage the reversal.
commit --amend
to effect the reversal on the cherry-picked commit.
Upvotes: 1
Reputation: 6038
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.git revert HEAD
– Make yet another commit, that adds back the unwanted changes. This is the double-revertgit rebase -i HEAD~3
). This commit now becomes free of the unwanted changes, for those are in the second commit.Upvotes: 4
Reputation: 143329
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.
Upvotes: 22
Reputation: 13716
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
Upvotes: 343
Reputation: 77620
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.
Upvotes: 13
Reputation: 793369
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.)
Upvotes: 55