The most surprising thing about Git commit messages is that their primary purpose isn’t to describe what changed, but why it changed.
Let’s say you’re looking at a diff and see a function that was modified to return false instead of true under a specific condition.
- return true;
+ return false;
A commit message like "Fix bug" or "Return false" tells you what happened. It’s factual, but utterly useless for understanding the context or the implications of that change months or years down the line.
commit abcdef1234567890abcdef1234567890abcdef12
Author: Jane Doe <jane.doe@example.com>
Date: Mon Oct 26 10:30:00 2023 -0500
Refactor user authentication flow
The previous authentication mechanism was overly complex and prone to race conditions.
This commit simplifies the flow by introducing a single, atomic check for user
credentials before granting access. This reduces the attack surface and improves
performance by eliminating redundant database lookups.
This commit message, however, explains why the change was made. It tells us about the problem being solved (complexity, race conditions, attack surface, performance) and the benefit of the solution (simpler flow, atomic check, reduced attack surface, improved performance). This is the information that helps future developers understand the intent behind the code, debug issues, and make informed decisions about future modifications.
Here’s a typical commit message structure that facilitates this "why":
Subject: Concise summary of the change (imperative mood, < 50 chars)
Body:
- Explain the problem this commit solves.
- Describe the approach taken.
- Discuss any trade-offs or alternative solutions considered.
- Link to relevant issues or tickets.
- Provide context that isn't obvious from the code itself.
The subject line should be a command. "Fix bug" is a noun phrase; "Fix bug" is an imperative command. It’s like issuing an order to the codebase. "Add feature" is okay, but "Add user profile editing" is better. "Refactor authentication" is good, but "Refactor authentication to use JWT" is more specific.
The body is where the real meat is. Imagine you’re explaining this change to a colleague who has never seen the code before. What do they need to know to understand why this particular piece of code exists in its current form?
Consider this commit:
commit fedcba0987654321fedcba0987654321fedcba09
Author: John Smith <john.smith@example.com>
Date: Mon Oct 26 11:00:00 2023 -0500
Implement rate limiting for API requests
The API was experiencing intermittent performance degradation due to
uncontrolled bursts of traffic from a few aggressive clients. This
commit introduces a rate limiter middleware that restricts the number
of requests a client can make within a given time window.
The current implementation uses a token bucket algorithm with a limit
of 100 requests per minute per IP address. This was chosen to balance
API availability with protection against abuse. Alternative approaches
like fixed window counters were considered but deemed less effective
against certain burst patterns.
See issue #123 for more details on the performance impact.
This commit message clearly states the problem (performance degradation due to traffic bursts) and the solution (rate limiting middleware). It also provides specific details about the implementation (token bucket, 100 requests/minute/IP) and the reasoning behind the chosen approach (balancing availability and protection, why alternatives were rejected).
When writing commit messages, think about the future. You or a teammate will eventually have to understand a change made months or years ago. A well-crafted message acts as a historical record, preserving the context and intent that would otherwise be lost. It’s the difference between staring at a cryptic diff and understanding the narrative of your project’s evolution.
The next step after mastering commit messages is understanding how to use Git’s interactive rebase to craft those messages effectively before they’re even committed.