Git time travel primer

2013-08-21

Using the power of git we can travel back in time and change our commits, this post will briefly introduce the idea of combining 2 commits into 1.

Apologies for the brevity, here I assume a certain working knowledge of git, I hope to come back later and write a more thorough guide.

For the code segments any lines beginning with $ are input, other lines are output.

Let's make a small git repo

$ mkdir sandbox
$ cd sandbox
$ git init

make it look busy

$ echo "hello" > file
$ git add file
$ git commit -m "first commit"
$ echo "world" >> file
$ git commit -a -m "second commit"
$ echo "how are you?" >> file
$ git commit -a -m "third commit"

peek at our file

$ cat file 
hello
world
how are you?

look at the mess we have made with

$ git log --oneline
2c83139 third commit
28a5b63 second commit
2e3850d first commit


We want to combine the second and third commits into one, git rebase -i can do this.

We have to give it a commit where we want to rebase 'upto', so here we want to smash every commit 'upto' our 'first commit' which has the hash 2e3850d.

We could instead refer to our first commit by the shorthand HEAD~2 which means '2 commits before HEAD'.

# could instead use: git rebase -i HEAD~2
$ git rebase -i 2e3850d

Git will then open our editor and display the following

pick 28a5b63 second commit
pick 2c83139 third commit

'pick' means keep the commit, 'squash' means combine the commit with the commit above it in the list

pick 28a5b63 second commit
squash 2c83139 third commit

save and exit the editor, immediately git relaunched the editor to make a new commit message

# This is a combination of 2 commits.
# The first commit's message is:

second commit

# This is the 2nd commit message:

third commit

all lines not beginning with # will becomes the new commit message.

change the commit message to 'second and third commit combined' and then save and exit your editor.

then inspect the mess

$ git log --oneline
2d87748 second and third commit combined
2e3850d first commit

notice the 'second commit' and 'third commit' commits are gone and have been replaced by 'second and third commit combined'.


Say we want to combine our remaining two commits, my usual trick of using the commit I want to squash 'upto' doesn't work here as there is no commit 'before' the 'first commit'.

So instead we have to use a little reset trick:

$ git reset --soft HEAD~

this undoes our top commit but leaves any changes still checked in (as if we had added them with git add but not yet done the git commit)

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   file
#

we can then add these to the now top commit 'first commit'

$ git commit --amend

again we are dropped into our editor to change the message, after saving and quitting the editor we can inspect the state of our sandbox

$ git log --oneline
486718b all my commits as one (first, second and third)

if we look at our file

$ cat file 
hello
world
how are you?

we can see all our changes are there and have now been combined into a single commit.