Why I Git Commit Too Much

I recently joined a new project, and one of my teammates asked me, “Why do you commit so much?” I decided to write this post to explain my reasoning to him and anyone else who might come across my work in the future.

Small commits can be your best friend.

Just like functions, small commits focus on one thing: a single change. This forces our commit messages to be more descriptive (sorry guys, “fixed some stuff” is not going to cut it anymore).

Let’s take a look at an example:

git commit -am "Updated 'Contact us' to 'Need Help? Contact Us!'"

In this case, you could just look at the changes made in the commit, but why look at the code when the description is right there in front of you? Small commits make it extremely easy to find a specific change that went in, especially when traversing a list. They also make it simple to see how the project was built, piece by piece.

1. They simplify code review.

Small commits make code review much easier. They let you review the changes one at a time and share the author’s mindset. The commits tell a story as if the author were explaining the changes in person.

2. They help you share knowledge.

Recently, I learned that adding a line break after a return statement in JavaScript is the same thing as returning nothing (if you’re curious, see this Stack Overflow answer for an explanation). I removed the line break and committed the result:

git commit -am "A return followed by a line break doesn't actually return anything"

Thanks to small commits, I was able to share this knowledge with my teammates, knowing it would be saved forever. If there’s one thing I’ve learned as a programmer, it’s that there is a reason for everything. Sometimes, the reasoning isn’t clear and a code comment doesn’t make sense (can you imagine every JavaScript function return having a comment saying “Line breaks will not actually return this data”). Small commits help fill this gap.

3. They can help fix your mistakes.

No developer is perfect. Sometimes, we head down the wrong path and realize something we tried just isn’t working out. Perhaps we tried to bold some text by adding a CSS class and tweaking some properties, but the results didn’t look that great. Now what?

We could manually undo the change, but it’s easy to miss something (for example, you might remove the CSS class from the HTML element but forget to remove the definition in the CSS file). git revert is an extremely easy way to undo a change without having to remember the initial state.

On a similar note, every one of us has written a bug, and even fixed a bug while introducing three more. Usually, we are able to find the root issue through using breakpoints and reanalyzing the problematic logic. However, there are times when the problematic logic just cannot be discovered, no matter what debugging steps we take.

git bisect is here to save the day! It first asks to provide a “good” commit (one that doesn’t experience the bug in question) and a “bad” commit (usually, it’s the latest commit). It then executes a binary search, testing various commits as “good” or “bad”.

It’s extremely important that every commit is buildable and the tests all pass. Otherwise, it will be impossible to verify whether a commit is “good” or “bad”. In the event that a broken commit sneaked in (from a teammate, of course, because we’re good boys and girls), git bisect skip can be used to skip over it (just cross your fingers that this commit is not the cause of the bug). At the end, git bisect skip will return the commit that introduced the bug. The problematic piece of code should be completely obvious and a fix should should already be cooking up.

4. They’ve got my back at standup.

If I had a penny for every time I blanked out on my previous day’s work during standup, I would be a millionaire. I once tried making a list of everything I worked on throughout the day, but it was tedious—and redundant, since my small commits shared the same information. Sure, no one wants to be that guy who has a laundry list of things he worked on, but skimming through the commit history is a great refresher (pro tip: try picking the top three things out of the list).

5. They let me cherry-pick changes.

Consider this scenario: while working on a separate task, Joe Bob added some new strings to the translations file that was just received. He also started incorporating these into his views, but he still has failing tests. Susan just started her task and realized that she needs some of these strings. It wouldn’t make sense to merge Joe Bob’s branch into hers, since it’s still a work in progress. Susan could just redefine these translations herself, but she fears the dreaded merge conflict. Since Joe Bob uses small commits, Susan is able to cherry-pick the commit that Joe Bob introduced the translations by running:

git cherry-pick some-hash

Summing Up

It’s okay to go down the wrong path, then revert your changes. No one cares about stupid little mistakes—if anything, it shows you care about bettering the project and ultimately yourself. If you’re contributing to an open source library, it’s usually better to have one commit per feature or fix. To get the best of both worlds, you can squash all of your tiny commits into one before you publish your changes. And for anyone curious, I’ve been on projects with well over 20,000 commits and experienced no slowness with git.

Do you commit too much?

Conversation
  • Bertrand says:

    I can’t agree more with your approach.
    I always start a new branch when developing new features (even small ones) or refactoring some code. This way I can easily `git rebase` my all work in order to improve readability of the log.
    I have created some Git alias to help me with this:
    “`
    ; Save current work
    save = “!f() { git add -A && git commit -m \”SAVEPOINT: ${1}\”; }; f”
    ; Come back to last commit
    undo = reset HEAD~1 –mixed
    ; Reset to original
    wipe = “!f() { git add -A && git commit -qm \”WIPE SAVEPOINT: ${1}\” && git reset HEAD~1 –hard; }; f”
    ; What the hell i have done for the last week
    standup = !”git log –reverse –branches –since=1.weeks –author=$(git config –get user.email) –format=format:’%C(cyan) %ad %C(yellow) %h %Creset %s %C(green)%d’ –date=local”
    “`

    Maybe it can help others.

    • Jeff Burt Jeff Burt says:

      Hey there! I’m happy to hear we have similar approaches. Thanks for sharing your aliases – I tried out the standup one and it was pretty neat!

  • I’ve said commit messages should tell a story.

    The real story about what a team faced when building the system.

    It’s worth reading it later, mainly in code reviews.

    Nice post with a very practical approach. Congrats.

    • Jeff Burt Jeff Burt says:

      Hey Vinicius, thanks for stopping by! I’m glad you enjoyed my post!

  • Todd Wolfson says:

    Nice explanation =) As a heads up, here are a few more commands to add to your toolbelt:

    – `git add -p` – Stage specific parts of files instead of all changes
    – Allows for more granular commits (e.g. fixed up lint errors, relocated function)
    – Replacement for `git commit -am` by using `git add`/`git add -p` and `git commit -m`
    – `git checkout -p ` – Pluck specific parts from commits
    – Allows for more granularity than a `git cherry-pick`
    – `git checkout -` Checks out last used commit
    – Useful for visiting a specific ref and then jumping back

    • Jeff Burt Jeff Burt says:

      Thanks for those great additions, Todd! I actually use GitHub’s GUI for committing. I find myself committing individual lines quite a bit (as opposed to entire files) and the GUI makes it effortless. For everything else, I use the terminal (fetching, merging, pushing, etc).

    • Stefan says:

      ‘git add -p’ is a mighty tool, but may generate files in the history that never existed like than on disk.

  • Comments are closed.