Without proper care, your Git history can easily become a jumbled mess. Writing good commit messages is important, but so is the actual content of those commits. Here's some helpful guidelines to improve their quality.

Introduction

Taking care of your commit history is important. When teaching Git, we often reference Chris Beam's guide on writing commit messages. However, equally important is what you actually put into those commits. Below are a few guidelines you can follow to improve commit readability, traceability of changes, as well as making the review process easier by boiling down the commit to essential changes..

1. Include only a single change

When making a commit, strive to have it only include a single change, and to have that change clearly reflected in its message. This makes the impact of a commit clear and prevents side-effects when reverting or merging commits. This is commonly referred to as crafting an atomic commit.

2. Reference an issue tracker 

When working on an issue, limit your changes to solving only that issue. Also remember to reference the issue ID in your commit message. This will make it easier to trace changes back to their parent issue, while also lowering the odds of a fix breaking something unrelated.

3. Separate out-of-context tests

When committing changes, you’re likely adding tests to verify those changes. As a rule of thumb, any tests that can pass without the changes you’re making should be committed separately. While increasing test code coverage is always welcome, mixing regression tests with new code coverage goes against the principle of the atomic Git commit.

4. Avoid mixing code and style changes

Style changes like fixing indentation and adding/removing trailing whitespaces are fine, but make a commit harder to review as you’re touching more lines than necessary. Here are a few things to keep in mind:

  • When cleaning up by, e.g., re-organizing imports or deleting dead code, do so in separate commits.
  • If you have documentation in your repository, wrapping the lines at e.g. 80 characters will reduce the diff in the modified paragraphs.
  • Add a linter to your CI tollgate to avoid having to make future style changes.
  • Use tooling to help keep a consistent style. For example, Black for Python can highlight style violations.

Some editors have opinions on styling and may automatically make style changes. This can be an issue when working in a big team where everyone may not be using the same editor. Consider using EditorConfig, which lets you write rules in a YAML file which tells the editor how to style different types of files. While newline settings may also be handled by Git (see core.autocrlf), EditorConfig is more powerful and expressive. Providing guidelines on how to configure an IDE to adhere to the code style is another option.

# top-most EditorConfig file
root = true
[*]
indent_style = space
trim_trailing_whitespace = true
indent_size = 4
end_of_line = lf
insert_final_newline = true
charset = utf-8

5. Minimize the lines that future commits will touch.

In certain languages, you can minimize the noise future changes will introduce. In Python, use trailing commas in dictionaries to avoid the next commit having to touch two lines to add one item. In Haskell, you can do the same by adding a comma at the start of the line.

In the Python example below, the new entry only touched a single line instead of having to touch two.

 car = {
   "brand": "Ford",
   "model": "Mustang",
"year": 1964,
 }

Helpful Git tools

If these guidelines seem like a bother to follow in practice, fear not. Git provides quite a few tools to help you craft those clean atomic commits.

  • git add --patch or git gui allow you to interactively select which code changes to stage
  • git commit --amend allows you to redo your latest commit, in case you made a mistake
  • git rebase -i allows you to rewrite your local history if you’re looking to clean up multiple commits

Conclusion

In essence, a Git commit should be an atomic change that only includes the code, tests and documentation of a single change. The guidelines above will help you craft clean commits, keep a tidy history and ease your review processes. In addition, future changes to existing features  will find the original commits an excellent reference point or template for the work that needs to be done. It may take a little discipline at first, but the reward is definitely worth the effort.