Need for Rebasing

We’ve all been there. We make a change to some source code, commit and push to master, only to get this horrid message in response:

Pushing to (some-remote-server)
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to '(some-remote-server)'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Without thinking clearly1, you may have typed: git pull

Now you are confronted with the Merge branch message, leaving your previously nice and linear Git history with a twig:

git-twig-2.png

A twig is what I call an unintentional Git branch caused by nothing more than a straight-forward merge.

What to do?

Rebasing for the Win!

Since your change (and the icky merge) is local to your repository (you haven’t issued a push to the remote repository), manipulate the Git history with the rebase command. While the word rebase sounds scary, it can be pretty simple.

Cleaning our history is a two-step process:

Look for the Parent

Issue a git log --pretty=oneline --graph to get a short listing of the commits.2

a357445 * Merge branch 'master'
        |\
3a8dec0 | * Added a license to the end of the readme.
b47700f | * Added a readme explaining the project
9aea7d5 * | Good quotes from Life of Brian
        |/
d956c7c * Found something from Flying Circus
c8f5e02 * Found something from Holy Grail
...

You need the commit ID of the trunk before the twig happened. In the example above, this would be d956c7c. The characters |/ point to the commit you want. ;-)

Interactive Rebase!

Rebase with the --interactive setting, as in:

git rebase --interactive d956c7c

Your editor will display your change at the top, and the commits from the other shmucks on your team underneath. Note: The commits are in historical order, with the oldest entry at the top (which is yours). Don’t be surprised since the log command puts the oldest entry at the bottom.

pick 9aea7d5 Good quotes from Life of Brian
pick b47700f Added a readme explaining the project
pick 3a8dec0 Added a license to the end of the readme.

Simply move your commit to the bottom3, so it is now the newest entry:

pick b47700f Added a readme explaining the project
pick 3a8dec0 Added a license to the end of the readme.
pick 9aea7d5 Good quotes from Life of Brian

Save and exit out, and your are done!

Summary

That’s right, a simple two step process. Check the logs and you will see a nice clean history, with no annoying twigs. In my case, it looks like:

2cd1c60 * master Good quotes from Life of Brian
3a8dec0 * origin/master Added a license to the end of the readme.
b47700f * Added a readme explaining the project
d956c7c * Found something from Flying Circus

I suggest practicing this. Practice how? Download the following two scripts (not without looking at them first):

Running them creates a clone of a Git repository in some temporary directory, e.g. /tmp/sandbox:

$ ./gitrepo-history-conflict.sh /tmp/sandbox
# ...lots of git messages...
All done. Checkout the goodies:   /tmp/sandbox/my-proj

$ ./gitrepo-history-nice.sh /tmp/sandbox
# ...blah, blah, blah...
All done. Checkout the goodies:   /tmp/sandbox/da-quotes

The first repository has the commits conflict with the unstaged changes, and the repository created by the second script doesn’t. Jump in there and have fun figuring out the correct way to keep your Git history tidy.4

Footnotes:

1

Save yourself a world of pain by issuing pull commands with the --rebase option:

git pull --rebase

Better yet, have this the default by setting this in your $HOME/.gitconfig file:

git config --global branch.autosetuprebase always

Of course, this only works for new clones and branches, so you may want to do:

git config --global branch.*branch-name*.rebase true

Note: If you are using Magit, then set this up as your default:

magit-rebase-always.png
2

I often create the following alias in my .gitconfig file:

[alias]
  lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative

This allows me to see a nice, colored history of changes by typing: git lg.

Actually, who am I kidding. I’m always using Magit which does this by default. ;-)

3

If you use Emacs, simply type M-n (Alt n) to move the entry commit entry down.

4

I guess I never really explained why a clean Git history important. While clarity is important, rolling back changes in a linear history is easier. The review system, Gerrit, really depends on a linear history, and this is my primary motivation for such effort.