More than a year ago I described a very simple Git rebase workflow, where all we were interested in was just fixing some mistakes in a past commit. Let us now go a little bit deeper. One thing I had always trouble with was splitting a commit in two (or more). While there are many tutorials about this on the internet, I wrote my own, even though it turned out that it is not better than the other ones. Go figure. (One advantage is that I have it on my website, so that I won’t need to do much searching in case I need it.)
(Of course, if we mean the last commit, we don’t really need fancy rebase
– we can just git reset HEAD^
, stage some things, make the first commit, and then proceed to the second one (or more). The tricky part is editing earlier commits.)
Let us begin almost like previously.
cd /tmp rm -rf rebase-edit-tutorial # in case you run this again git init rebase-edit-tutorial cd rebase-edit-tutorial echo 'Initial commit' > file.txt git add file.txt git commit -m 'Zeroth commit' echo -e 'This commit\nwill be split in two.' >> file.txt git add file.txt git commit -m 'First commit to split' echo -e 'This commit will stay.' >> file.txt git add file.txt git commit -m 'Another commit'
We now want the two lines of the First commit to split
to be added in two subsequent commits. Here’s how we can do it. Start with git
rebase -i HEAD^^
. Change the pick
in the first line to edit
, save and exit the editor, and then say git reset HEAD^
. (Note that this is a different HEAD
this time! To see what exactly HEAD
is every time, say e.g. git rev-parse HEAD
.) Here’s the explanation: rebasing puts us (more or less) in a state “right after HEAD^^=”.
Since we want to change =HEAD^^
, we need to go back just a bit further, to the moment before we committed it. This is what git
reset --mixed HEAD^
does – and by the way, --mixed
is the default, so we can omit it. (Actually, this takes us back even further, to the point before git add
, i.e., not only does it reset the HEAD
, but also the staging area.)
We can now add and commit the first line we introduced in our commit. Say git add -p
and then e
. Delete the second line beginning with +
and exit the editor. Say git commit -m "First part"
. Now you can just git add file.txt
and git commit -m "Second part"
. Finish off with git rebase --continue
and you’re done.
One thing that is not mentioned in many tutorials is that this way, you completely lose the commit message of the commit you have just split in two (or more) parts. If that is a problem, there is a -c
option to the git-commit
command, which accepts a commit hash and uses the commit message from that commit as the basis for the new message. (There is also a variant, -C
, which does not let you edit the message – it just takes another one and applies it.) Of course, the commit is not visible in the log now, but it is not gone from the repository – you can look it up in git reflog
, or git rebase
--abort
, make a note of the commit’s hash and repeat the rebase.
But whenever you use a computer and feel the need to “make a note of something” manually to reuse it later, it is a sign that either you are doing something wrong, or the author of the tool you are using did something wrong. Now guess who is smarter, you or Git developers? (Well, that was mean, sorry.) Here is a trick: you can say git
rev-parse :/'First commit to split'
, or even just git rev-parse
:/First
. What is this? Well, whenever Git wants you to give a commit hash, you can use the :/regex
syntax, which gives you the newest commit reachable from any ref (like HEAD
or branches) whose commit message matches regex
. How cool is that!? The :/
syntax can do even more – say man gitrevisions
to learn about it and other ways to specify revisions. You may thank me later.