Proper Git

Git is one of the most wonderful tools developers can use to make their lives better. As Linus himself put it best: “If you actually like using CVS, you shouldn’t be here. You should be in some mental institution.”

However, Git is often underused and abused with badly written messages, junk commits and weird branching. You should not treat Git, or any other SCM tool for that matter, as a way to “Save” your code, just like you would save your progress in a game. Git is about collaboration and delivering clear sets of changes. Every commit should be a small story for your team - it should be worth telling and told well. And it doesn’t matter whether you work on your repo alone or with a team, you should use it as if the whole world will be analyzing every change.

Why commit messages are important

Let’s say you are tracking down a difficult to reproduce bug. You look at your Git history trying to find what did other people change just before the bug was spotted, and you have to look at this:

6e03503 Typo
d9c9874 merge master
4c6a726 fix conflicts
9b193b5 HEAD :)
49329d1 Merge branch 'master' into feature/item_image_shadow
7b8fc0b Merge branch 'master' into feature/responsive_ads
a2e8057 Merge branch 'master' into feature/responsive_ads
aced22b review: code style
ebede6b = -> +=
df34e54 unit test
ce0559c Fix - the road not taken...
ad32248 merged master
15830db minor refacotr
ba4a3c2 Swipe, swipe, swipe
1d5904d cleanup
3a4d808 Improvements
13eee09 Fixes
9edc0ac Bugfix
885ee70 Fix more tests
86fcf51 Fix some tests
e8807ee Merge branch 'master' into bugfix/3841-api-get-catalog-tree
dc84a3e Fixes after review
9280e89 Well. We'll see.
864a86e remove binding.pry
1010101 Commit committed....
ed2e4a1 Oops
1c691eb Fix bug
d29b398 unnecessary line
93a49c5 Spec fix
69570e6 Oops.
f00f000 Working on tests (haha)
6c5dcd6 Fix for A/B tests
7170ac6 fix 1 test
5ec0ee7 Final fix for jenkins time issue
0a4a6a2 Final fix for jenkins time issue
049bec9 Fixes
f43fbad Cleanup
5fd1375 Fix migrations
aac2c01 fix migrations
b63de32 fix migrations
639a751 merge fixed
9c2e48a Missing comma
8e0d34e Viva performance!
dfefb53 Variable renamed

It’s full of noise, unnecessary redundancy and you would actually have to inspect most of these commits to make sure what happened there. And probably keep a separate log with commit hashes that you already looked at, because there are so many fixes, that it would be pretty challenging to remember which of them you went through.

These are actual commits from our codebase (and a couple of bonus examples from whatthecommit.com). Thankfully, they are more than a couple of years old - our Git etiquette improved a lot.

And this is how a relatively good set of commits should look like:

cb6a291 Add item groups index API action for admins
8ee0b9d Use different template for system messages
bdd82ff Allow retrying failed debits
afa12d5 Add date validation on bank account birthdate
f7d7a39 Disable dialog open button after first click
0f9b7b3 Enable SSL in mailbox for all countries

This log is meaningful and has much more value per line. People cared about writing good commit messages, and most probably took extra effort to rebase, amend and rewrite Git commits to get rid of the noise, instead of doing it the easy way: git commit -am "Fix"

Whenever you write git commit -am "Fix", an octocat dies.

Anatomy of bad Git commits

You’re using master as your main branch and have a “blessed” repository, maybe on GitHub? Great! Now, how do you work with that?

Let’s say you want to add RSS feed to the website you’re building.

You get in the zone, and implement it in a couple of hours. While doing that, you also find a non-related bug, which you squash without mercy. And at the end of that great coding session, you commit:

git commit -am "Adding RSS feeds so that users can use a feed reader to get \
all our content on demand. Also fixed issue #124."

and then

git push origin master

What’s wrong with that?

  1. This commit should have been split into two or even more commits. When it’s hard to add different changes, i.e. they are in same files, you can do that pretty easily with interactive patch, using git add -ip.
  2. The commit message is too long. According to Git Commit Guidelines, short summary should be 50 chars or less. Use git commit -av to write your commit messages instead of doing it with -m "inline message" - you will see what you have changed, may notice that something is missing or broken, and you will almost always end up writing a much better commit message.
  3. Instead of writing what you did, you should write what the code will do when applied. So, rather than writing “Adding RSS feed” or “Adds RSS feed”, write in imperative: “Add RSS feed”. Tim Pope has a great post about writing good git commit messages.
  4. You should not commit directly to master unless your code was reviewed and tested, especially if you use GitHub, which provides the absolutely amazing “Pull Requests” feature. If you don’t use GitHub, then you should put your code up for a review in some tool like Review Board. And you should have used a branch - two branches, one for RSS feeds, another for the Issue #124.

If you have a CI build server, you should make it build branches, or even better, GitHub Pull Requests and get you to know if your changes would make the master build fail after you merge your changes before you merge your new code to master.

We use Jenkins with customized version of GitHub Pull Request builder plugin to provide a nearly instant feedback loop about your upcoming changes.

Nothing is final

Even though that is true, we have a rule of the thumb - nothing is final until it is merged with master in the blessed repository. You can use git revert <SHA1>, but that’s equal to failing to ride a unicycle in front of a big audience, so it should be avoided at all cost.

One commit per change

Sometimes, when you are working on a feature, you get to randomly change this and that, or perhaps throw in a small bugfix. Resist the urge to commit everything in one chunk. You may end up with a lot of commits, but since you should make them in your own branch, you can rewrite them using interactive rebase. So, if you made 3 commits, you can rebase them with git rebase -i HEAD~3.

Here is a good post about squashing commits.

Don’t Fix, Amend!

Have you ever committed something that was slightly wrong? Perhaps you included a mistype, or forgot to git add a newly created file? Resist the urge to add an extra commit to fix that, do git commit --amend instead. It’s also great for fixing mistypes in your commit messages.

And the greatest thing is when other folks review your pull request, you can simply amend the changes, without adding that pesky “Fix code review comments” commit.

Rebase is better than merge

When you pull the code from a remote repo, use --rebase option to rebase instead of adding a merge commit. And when you have a pull request open and GitHub cannot merge it automatically, do git rebase master instead of git merge master - your merge will be embedded in the last commit.

Remote branch is still your branch

These powerful history rewriting techniques come with a price. Your commits change, and you cannot simply sync them with remote repository anymore. So, unless someone else is coding on your branch, you will have to use git push --force to update all your changes. It’s scary, but it’s safe while you don’t get too confident with it and don’t try it on your blessed master.

More on rewriting history

You can find a set of elaborate instructions about rewriting history in this great article.

Summary

  • Respect Git conventions by writing commit messages properly
  • Use pull requests if you’re on GitHub
  • Don’t work on master directly
  • Redo your commits when needed

Learn to use Git well as it’s one of the best tools a developer can have.