Without creating a branch and doing a bunch of funky work on a new branch, is it possible to break a single commit into a few different commits after it\'s been committed to
From git-rebase manual (SPLITTING COMMITS section)
In interactive mode, you can mark commits with the action "edit". However, this does not necessarily mean that git rebase expects the result of this edit to be exactly one commit. Indeed, you can undo the commit, or you can add other commits. This can be used to split a commit into two:
Start an interactive rebase with
git rebase -i <commit>^
, where<commit>
is the commit you want to split. In fact, any commit range will do, as long as it contains that commit.Mark the commit you want to split with the action "edit".
When it comes to editing that commit, execute
git reset HEAD^
. The effect is that the HEAD is rewound by one, and the index follows suit. However, the working tree stays the same.Now add the changes to the index that you want to have in the first commit. You can use
git add
(possibly interactively) orgit gui
(or both) to do that.Commit the now-current index with whatever commit message is appropriate now.
Repeat the last two steps until your working tree is clean.
Continue the rebase with
git rebase --continue
.
Easiest thing to do without an interactive rebase is (probably) to make a new branch starting at the commit before the one you want to split, cherry-pick -n the commit, reset, stash, commit the file move, reapply the stash and commit the changes, and then either merge with the former branch or cherry-pick the commits that followed. (Then switch the former branch name to the current head.) (It's probably better to follow MBOs advice and do an interactive rebase.)
Previous answers have covered the use of git rebase -i
to edit the commit that you want to split, and committing it in parts.
This works well when splitting the files into different commits, but if you want to break apart changes to the individual files, there's more you need to know.
Having got to the commit you want to split, using rebase -i
and marking it for edit
, you have two options.
After using git reset HEAD~
, go through the patches individually using git add -p
to select the ones you want in each commit
Edit the working copy to remove the changes you do not want; commit that interim state; and then pull back the full commit for the next round.
Option 2 is useful if you're splitting a large commit, as it lets you check that the interim versions build and run properly as part of the merge. This proceeds as follows.
After using rebase -i
and edit
ing the commit, use
git reset --soft HEAD~
to undo the commit, but leave the committed files in the index. You can also do a mixed reset by omitting --soft, depending on how close to the final result your initial commit is going to be. The only difference is whether you start with all the changes staged or with them all unstaged.
Now go in and edit the code. You can remove changes, delete added files, and do whatever you want to construct the first commit of the series you're looking for. You can also build it, run it, and confirm that you have a consistent set of source.
Once you're happy, stage/unstage the files as needed (I like to use git gui
for this), and commit the changes through the UI or the command line
git commit
That's the first commit done. Now you want to restore your working copy to the state it had after the commit you are splitting, so that you can take more of the changes for your next commit. To find the sha1 of the commit you're editing, use git status
. In the first few lines of the status you'll see the rebase command that is currently executing, in which you can find the sha1 of your original commit:
$ git status
interactive rebase in progress; onto be83b41
Last commands done (3 commands done):
pick 4847406 US135756: add debugging to the file download code
e 65dfb6a US135756: write data and download from remote
(see more in file .git/rebase-merge/done)
...
In this case, the commit I'm editing has sha1 65dfb6a
. Knowing that, I can check out the content of that commit over my working directory using the form of git checkout
which takes both a commit and a file location. Here I use .
as the file location to replace the whole working copy:
git checkout 65dfb6a .
Don't miss the dot on the end!
This will check out, and stage, the files as they were after the commit you're editing, but relative to the previous commit you made, so any changes you already committed won't be part of the commit.
You can either go ahead now and commit it as-is to finish the split, or go around again, deleting some parts of the commit before making another interim commit.
If you want to reuse the original commit message for one or more commits, you can use it straight from the rebase's working files:
git commit --file .git/rebase-merge/message
Finally, once you've committed all the changes,
git rebase --continue
will carry on and complete the rebase operation.
If you have this:
A - B <- mybranch
Where you have committed some content in commit B:
/modules/a/file1
/modules/a/file2
/modules/b/file3
/modules/b/file4
But you want to split B into C - D, and get this result:
A - C - D <-mybranch
You can divide the content like this for example (content from different directories in different commits)...
Reset the branch back to the commit before the one to split:
git checkout mybranch
git reset --hard A
Create first commit (C):
git checkout B /modules/a
git add -u
git commit -m "content of /modules/a"
Create second commit (D):
git checkout B /modules/b
git add -u
git commit -m "content of /modules/b"
git rebase --interactive
can be used to split a commit into smaller commits. The Git docs on rebase have a concise walkthrough of the process - Splitting Commits:
In interactive mode, you can mark commits with the action "edit". However, this does not necessarily mean that
git rebase
expects the result of this edit to be exactly one commit. Indeed, you can undo the commit, or you can add other commits. This can be used to split a commit into two:
Start an interactive rebase with
git rebase -i <commit>^
, where<commit>
is the commit you want to split. In fact, any commit range will do, as long as it contains that commit.Mark the commit you want to split with the action "edit".
When it comes to editing that commit, execute
git reset HEAD^
. The effect is that the HEAD is rewound by one, and the index follows suit. However, the working tree stays the same.Now add the changes to the index that you want to have in the first commit. You can use
git add
(possibly interactively) or git gui (or both) to do that.Commit the now-current index with whatever commit message is appropriate now.
Repeat the last two steps until your working tree is clean.
Continue the rebase with
git rebase --continue
.If you are not absolutely sure that the intermediate revisions are consistent (they compile, pass the testsuite, etc.) you should use
git stash
to stash away the not-yet-committed changes after each commit, test, and amend the commit if fixes are necessary.
It's been more than 8 years, but maybe someone will find it helpful anyway.
I was able to do the trick without rebase -i
.
The idea is to lead git to the same state it was before you did git commit
:
# first rewind back (mind the dot,
# though it can be any valid path,
# for instance if you want to apply only a subset of the commit)
git reset --hard <previous-commit> .
# apply the changes
git checkout <commit-you-want-to-split>
# we're almost there, but the changes are in the index at the moment,
# hence one more step (exactly as git gently suggests):
# (use "git reset HEAD <file>..." to unstage)
git reset
After this you'll see this shiny Unstaged changes after reset:
and your repo is in a state like you're about to commit all these files. From now on you can easily commit it again like you usually do. Hope it helps.