A good git commit
Published on Last updated on
A good git commit is not always easy to create, as it is more an art than science in many aspects. While that sounds very flowery, it just means you, as the committer, need to exercise your judgement. While that might sound nice, some guidelines make life a lot easier.

Of course this is not required at all: you can also just use git as a snapshotting mechanism, but in general, I find it nice to use tools to their fullest, so why should git be any different?
How big should a commit be?
While answers to this question vary, a popular convention is called atomic commits.
Atomic commits are commits where each commit contains a single, complete unit of work. Single means the unit of work only affects one aspect at a time, but not necessarily. The complete part of the name is more important: the commit should be complete on its own.
Of course, this still leaves a lot of wiggle room. When is a unit of work complete? I find a good rule of thumb to be asking if the commit would be mergable on its own. This means the commit has no unfinished work (e.g. missing CSS styles or unimplemented functions), it includes passing tests, and all implemented parts work properly.
Note that this does not mean the commit should necessarily be useful. Sometimes, such a commit can be extracting some code into a reusable function, without including the second use of the code.
For example, let us say we want to add a number field to some form in a web application. When we begin working on it, we notice that there is no number field in our framework yet, and that the validation logic for all fields is copied and pasted for each field type.
A series of atomic commits for this change might look like this:
$ git log --oneline e4a5d6f (HEAD -> feature/number-field) Add age field to user profile c3b4a5d Add numerical field type a2b3c4d Extract field validation into utility
The first commit, a2b3c4d
, extracts the duplicate validation code into a re-usable utility.
It is a complete unit of work, so it also contains tests for the new validation utility.
But it is also a single unit of work, so it does not include the new field type.
The second commit c3b4a5d
adds a numeric field component.
This commit can, of course, use the new validation utility we added in the previous commit.
If there are UI tests, it also includes them.
But this commit does not add any uses of the new field type.
The final and most recent commit will then finally add the new field in the form where it was requested.
Note that there are other possibilities. For example, imagine the tests from the first commit revealed a bug. Do we then add a commit zero, which fixes the bug? Do we let the tests fail in the first commit and fix the bug afterwards? Do we just fix the bug in the first commit? All these approaches have benefits and drawbacks.
The main benefits of using atomic commits are:
- Atomic commits make it easier to track regressions.
In the example above, if our refactor added a regression, the person investigating it later can clearly see it is a regression, since it was done in a refactor commit. Had we made one big commit, we might have to wonder if the regression was an intentional change for some cases we are not privy to. Similarly, things likegit bisect
rely on this convention to work properly. - Atomic commits make it easier to manipulate them.
For example, if we forgot a field type in the refactor, we can easily amend the commit, create a fix-up commit, or interactively rebase the changes. - Atomic commits force you to do changes more deliberately.
This prevents changing a lot of things and places at the same time, which increases the risk that something is forgotten.
Atomic commits have been around a long time, even from before git. Some useful resources on this are:
- Make Atomic Git Commits (2021), by Aleksandr Hovhannisyan.
- Atomic Commits to Version Control (2006), by Barney Boisvert.
- The Commit Guidelines from the Pro Git book by Scott Chacon and Ben Straub contain a section that you should "try to make each commit a logically separate changeset".
- The contribution guidelines of many big projects suggest similar things, like those from git itself.
Merging changes
Assuming you are using branches, at some point you will need to merge your changes from your branch back into the master branch. On GitHub this is done via pull requests, on Gitlab via merge requests, and the Linux kernel works with patches via e-mail. Whatever it is called, the concept remains the same: a set of commits are proposed for inclusion into some branch. In teams, there is often a review of these changes before they are accepted (or merged).
When merging, one approach is to take the commits you wish to merge and put them on the target branch. Relatively straightforward (at least conceptually for our purposes here).
An alternative is squash merging: when merging, all commits are squashed into a single commit and that commit is then included in the master branch. The problem with this approach is that it destroys the nice history and carefully crafted commits you created.
In my opinion, if you feel the need to squash commits when merging, e.g. "to keep the history clean", this is a symptom that you are using commits suboptimally. Of course, if the commits in the pull request are not atomic commits, squashing could help keep a at least somewhat clean history, so in that case it might be worth it.
For some reading on this, I recommend _I hate squash merges _ by Matthias Beyer.
TODO later
Note that most of these benefits only really shine if you use them in conjunction with proper change management (e.g. pull requests) and proper tooling. Note that even the book notes that they did not invent it. They adapted it from https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html.
Naming a commit
Now that we covered what goes into a git commit, the next step is naming the commit, i.e. writing the commit message. This part is perhaps even more important than how big exactly a commit is. Using atomic commits is useless if the commits do not have a good description.
From the Pro Git book, we find the following git commit message template:
Capitalized, short (50 chars or less) summary More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of an email and the rest of the text as the body. The blank line separating the summary from the body is critical (unless you omit the body entirely); tools like rebase will confuse you if you run the two together. Write your commit message in the imperative: "Fix bug" and not "Fixed bug" or "Fixes bug." This convention matches up with commit messages generated by commands like git merge and git revert. Further paragraphs come after blank lines. - Bullet points are okay, too - Typically a hyphen or asterisk is used for the bullet, followed by a single space, with blank lines in between, but conventions vary here - Use a hanging indent
We can distill this template and the other recommendation in the section Contributing to a Project into some rules:
- Separate the subject from the body with a blank line
- Limit the subject line to 50 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative form in the subject line
- Wrap the body at 72 characters
- Use the body to explain what and why vs. how
- Add metadata after the body, in the trailers
As with everything in this piece, this is not new information. For example, see How to Write a Git Commit Message by Chris Beams.
Subject line
The first five rules are about the subject line, which makes sense. The subject line is the most-read part of any commit, and is the only part shown in many commit lists (include tooling on the command line, but also on the web like on GitHub). You should assume the description will only be read by people actively looking into the history of a specific section of code.
Some of those rules are elementary: just follow them. For example, limit the subject line to 50 characters. Separate it with a blank line if a body or metadata follows it in the commit message. Do not write a period (or other punctuation in the rare cases those apply) at the end.
The most difficult one has to do with the content of the subject line: using the imperative form. The imperative form is the form used in sentences to give a command or instruction. Some examples are "Clean your room" or "Write this commit". Some examples of subject lines are then:
- Fix typo in documentation
- Refactor authentication code
- Enable database connection pooling
A good rule of thumb is to image your subject line in the sentence If applied, this commit will X, where X is your subject line:
- If applied, this commit will fix typo in documentation
- If applied, this commit will refactor authentication code
- If applied, this commit will enable database connection pooling
In addition to the imperative, many people employ a form of headlinese or telegraphic writing in the subject line. After all, just like in news headlines or telegrams, the subject line is severely restricted in length, so in many cases, "'unnecessary'" words are omitted out of necessity. And when something is done frequently, it often becomes a defining characteristic, even if the underlying cause no longer warrants it. For this reason, many write like this in all commit messages, even if there is enough space.
Description
Separated by an empty line from the subject line, the description of a git commit is an optional part that gives more information. It is sometimes called the body of a git commit, as an analogue to subjects and bodies in e-mail (quite possibly the inspiration for git, since the creators of git use a purely mail-based workflow). It is subject to rules 6 and 7.
The body of a git commit is much more relaxed. While text should generally be wrapped at 72 characters, there can be valid reasons to deviate from this occasionally (just like in a plain-text e-mail). For example, if putting URLs in the body, there is no use in splitting a URL.
Secondly, the body should provide additional information about the changes in the commit, if necessary. And remember, while the subject line is a general summary of the commit, the body is not. After all, the "'how'" of the changes is clear from the code itself; no need to repeat it in the body.
What is good for is additional context about why the changes were made. This can include why the changes are made, why the original solution is no longer good, or even an explanation why the original code never worked.
Metadata or trailers
Finally, after the body, and again separated by an empty line, metadata about the commit can be added. For example, referencing the issue number the commit fixes, referencing a related commit, or even author related data.
Again, git tools work with this as well.
For example, the git --signoff
feature will add a Signed-off-by: XXX
trailer to the commit message.
There is even a (lesser known) command git interpret-trailers
to parse this information.
These are often very specific to a project and might not be useful to you at all.
Example
As an example, lets take an arbitrary commit from the Linux kernel, commit 8694138
.
I have chosen a simple commit, whose message in full is:
selftests: drv-net: wait for iperf client to stop sending A few packets may still be sent out during the termination of iperf processes. These late packets cause failures in rss_ctx.py when they arrive on queues expected to be empty. Example failure observed: Check failed 2 != 0 traffic on inactive queues (context 1): [0, 0, 1, 1, 386385, 397196, 0, 0, 0, 0, ...] Check failed 4 != 0 traffic on inactive queues (context 2): [0, 0, 0, 0, 2, 2, 247152, 253013, 0, 0, ...] Check failed 2 != 0 traffic on inactive queues (context 3): [0, 0, 0, 0, 0, 0, 1, 1, 282434, 283070, ...] To avoid such failures, wait until all client sockets for the requested port are either closed or in the TIME_WAIT state. Fixes: 847aa55 ("selftests: drv-net: rss_ctx: factor out send traffic and check") Signed-off-by: Nimrod Oren <noren@nvidia.com> Reviewed-by: Gal Pressman <gal@nvidia.com> Reviewed-by: Carolina Jubran <cjubran@nvidia.com> Reviewed-by: Simon Horman <horms@kernel.org> Link: https://patch.msgid.link/20250722122655.3194442-1-noren@nvidia.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
The three parts of the commit message are clearly visible:
- The subject line is a short summary of the changes
- The body contains why the changes are necessary
- The trailers contain various information required by the kernel development team
When not to do all these things
Making good commits and proper commit messages take effort. Sadly, if some of the people working on the same project do not want to follow these conventions, a lot of the benefits become a lot less pronounced.
For example, if commits in your repository are just numbers or a series of characters for subsequent commits, it becomes much more difficult to effectively use the git history. In these cases, it might be fine to add a commit later to fix something without taking the effort to modify the proper commit.

Some images included on this page are licensed under the Attribution-NonCommercial 2.5 from https://xkcd.com/.