I recently covered what makes a good Git commit. In this post, I want to flesh out an important element of that: the commit message.
Writing a good commit message is one of those skills I tend to take for granted, but it’s surprisingly complicated. It requires empathy and discipline, and like the rest of source control hygiene, there’s a surprising diversity of practices and strong opinions.
I hope this post can provide a primer for those developers who’ve never received any mentoring or feedback on their source control habits. Even if you disagree with my preferences, I hope you take a moment to reflect on how your team commits code and whether your approach is meeting your long-term goals. I reference Git here, but these points are applicable to subversion or TFS or any source control system.
The advice in this list should serve you well on most teams, though it is more applicable if you follow my other advice on how to commit code, i.e., commit messages are easier to write for focused commits.
If you find yourself struggling to write a clear, concise commit message, it’s possible your commit is too large or unfocused.
Provide a Meaningful Summary – Summarize what changed at a high level; include meaningful information but not every detail. This advice is subjective, akin to the famous writing instruction, “Omit needless words.” In general you should assume the reviewer knows how to read code and can figure out system dependencies, but they won’t know what you were thinking or why you made the change.
In other words, six months from now, when you are reviewing commits and trying to figure out why a behavior got introduced, what information will you want to know?
Express Intention – Ideally your code is perfectly intention-revealing with clear names and exquisitely intuitive structure, but that’s not always reality, not for my code at least. Sometimes code is a little gnarly, and especially when you are changing that code, it’s good to include the goal of your change.
As an example, let’s say that I need to change the default sorting for a list of tasks to match feedback collected from user testing. Then six months later, I’m looking at a weird line of code and run
git blame, which message would be most helpful?
Commit message #1: “UI updates”
Commit message #2: “Update task list page”
Commit message #3: “Sort task list by most recently viewed.”
For me, #3 provides the most information, while requiring negligibly more effort to enter. I immediately know what the dev was trying to accomplish.
Assume No Context – I often see commit messages that make sense in the context of a branch, e.g., when you’re reviewing a PR, but when you look at them months later, they are unintelligible.
For example, if you’re working in a
feature/individualized_sort_settings branch, it’s natural to have that context in mind and write a commit message like “Add scaffolding for settings panel integration.” But once that branch gets merged, and you’re looking at that commit on main, the message loses the context of individual sort or whatever the branch was about. A message of “settings panel integration” has no hint that it’s actually about the sorting functionality.
Seek Consistency – While I believe in the advice above, it’s also tremendously important to be aware of the established patterns. There are various approaches to source control and writing commit messages that can work well – if everyone is following the same rules. The worst repos I’ve seen have had multiple teams, with different approaches.
If the norms of your team don’t meet the goals above, it’s worth having a conversation with the whole team. I don’t recommend unilaterally changing your commit habits.
These items are less universal. Many excellent engineers would vehemently disagree with me.
Present Tense – I like a commit message to be written in the present tense. I think a commit message should tell me what happens when I apply this commit to my branch. I want the message to tell me what this commit does, not what you did to accomplish it. I can see what you did by looking at the code diffs. Keeping the message in the present tense encourages a focus on intent. I also think it makes for a more pleasant git log in general, though that’s clearly subjective.
That said, I can live with past tense messages, and it does have some benefits, e.g., being able to extract commit messages and drop them into informal release notes.
Provide Breadcrumbs – One of the biggest pieces of missing context in a codebase is why decisions were made. Even if your code is perfectly clear in intention, it’s not always clear why that behavior is desired. Ideally we developers know our product and our users well enough to have educated, empathetic guesses on the reasons driving our system behavior, but sometimes, especially in complicated niche domains, decisions are not at all obvious.
In those cases, if I’m changing something in a way that is inherently confusing, I’ll sometimes leave a breadcrumb to help my future self track down why the change was made.
To extend the example above, if I felt it was confusingly weird to sort the task list that way, I might add a breadcrumb to the message like so: “Sort task list by most recently viewed, per October 2020 user testing.”
Hopefully we have more robust documentation elsewhere on why decisions were made, and we don’t want to reproduce all of that in commit messages. But if we include a breadcrumb, we’d at least have a trail to follow. It wasn’t just me having a random idea; it was based on something that can be researched.
Subject & Body – It’s not always necessary to have a long commit message, but if you do need to provide a lot of details, it’s preferable to have a short subject, followed by a blank line, and then body paragraphs.
In general, about 50 characters is a good limit for the subject line. Chris Beams has a great post on how to do this properly – along with other thoughtful tips on commit messages. The 7 rules of a great Git commit message.
Beware of Referencing Other Systems – In theory, I have no problem referencing other tracking systems, e.g., the ID of a Jira ticket or Clubhouse story. The problem is that they become a crutch. You start seeing commits like “Bug fix for #1284”.
I should never have to open another system to understand a commit message. It’s cool if there’s more context somewhere, e.g., a decision-tracking wiki, and there are some nice, automated workflows you can kick off by referencing other systems.
That said, the git logs should stand alone and assume that any external system can disappear at any moment – because they often do. I’ve seen codebases with source history going back decades, commit logs imported into Git from Subversion. Whatever tracking systems were used have long since been abandoned, but the commit messages live on.
Love Letters to the Future
“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” — Martin Fowler
As a team, we decide on conventions for our code and try to abide by them, maybe even enforce with a linter or style cop. The compiler or interpreter generally doesn’t care about our style choices. Those are for the humans interacting with the code.
Likewise, computers don’t care what a commit message looks like, but it affects the humans interacting with the codebase. Good programmers write commit messages that humans can understand.
To echo my earlier post, how we commit code can be as important as what code we commit. The code we write will be changed over time, but the commit message we write will live forever as a snapshot of our intent, what we thought the system should do and why.
Our commit messages are our love letters to the future maintainers of our code.
Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.