Your git commit history could be telling an epic tale of how your codebase evolved, or it could be a cryptic collection of "fixed stuff" and "updated things." The difference isn't just aesthetic—it's the line between a repository that teaches and one that confuses. Well-crafted commits don't just track changes; they document your code's journey in a way that helps your team and future you.
Commits as living documentation
Git commits are more than just snapshots of code at a point in time. When done right, they form a narrative that explains not just what changed, but why it changed. This creates a powerful layer of documentation that lives alongside your code, evolving with it rather than becoming stale like traditional docs often do.
Imagine jumping into a codebase and being able to understand:
- The reasoning behind major architectural decisions
- Why a particular approach was chosen over alternatives
- The context around critical bug fixes
All without having to interrupt teammates or dig through outdated wiki pages.
Here's how your commit history can transform from a confusing jumble to a clear narrative:
Versus:
The difference is night and day—one tells a story, the other is just noise.
The anatomy of a documentation-quality commit
Let's break down what makes a commit truly useful as documentation.
The commit message structure
A well-structured commit follows this pattern:
Short summary (50 chars or less)
More detailed explanation if needed. Wrap lines at
72 characters. The blank line separating the summary
from the body is critical.
- Bullet points are fine
- Explain what and why, not how (the code shows that)
Related-issue: #123
Real-world examples
Here's what not to do:
fix bug
And here's what to do instead:
Fix race condition in worker queue processing
The worker was not checking if an item was already being
processed, causing duplicate work and potentially corrupted
data when multiple workers picked up the same task.
- Added atomic locking mechanism via Redis
- Added unit test to verify concurrency handling
Related-issue: #234
See the difference? The second example tells a story that helps anyone understand the context and significance of the change.
Practical commit strategies
1. Atomic commits
Keep each commit focused on a single logical change. This makes your history readable and makes it easier to revert specific changes if needed.
# Instead of one massive commit with multiple unrelated changes:
git add .
git commit -m "Big update with lots of stuff"
# Make focused commits:
git add src/authentication/
git commit -m "Refactor authentication to use JWT tokens"
git add src/api/endpoints/
git commit -m "Add rate limiting to public API endpoints"
2. Use conventional commit format
Following a convention like Conventional Commits adds structure to your messages and enables automated tools. The basic format is:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Where type
can be:
feat
: A new featurefix
: A bug fixdocs
: Documentation only changesstyle
: Changes that don't affect the meaning of the code (white-space, formatting, etc)refactor
: A code change that neither fixes a bug nor adds a featureperf
: A code change that improves performancetest
: Adding missing tests or correcting existing testsbuild
: Changes that affect the build system or external dependenciesci
: Changes to CI configuration files and scriptschore
: Other changes that don't modify src or test files
Examples of conventional commits:
feat(auth): implement multi-factor authentication
fix(api): prevent SQL injection in search endpoint
docs(readme): update installation instructions
style(forms): align input fields consistently
refactor(utils): simplify date formatting functions
perf(queries): optimize database index for search
test(auth): add unit tests for password validation
This approach makes it easier to automatically generate changelogs and determine version bumps. For semantic versioning:
feat
commits typically trigger a MINOR version increment (x.Y.z)fix
commits typically trigger a PATCH version increment (x.y.Z)- Any commit with
BREAKING CHANGE
in the footer triggers a MAJOR version increment (X.y.z)
Git trailers are key-value pairs in the footer of your commit message, similar to HTTP headers. They're perfect for machine-readable metadata like issue references, co-authors, or review status.
Signed-off-by: Ris Adams <mail@risadams.com>
Reviewed-by: Jane Developer <jane@example.com>
Related-to: #123, #456
Fixes: #789
For more on how to leverage git trailers and other advanced git metadata features, see my article on Git Notes & Trailers: The Hidden Features You Should Be Using.
3. Reference related issues
Always link your commits to tickets, issues, or requirements when applicable.
Implement password strength meter
Adds visual feedback to users about password security
during registration process. Shows color-coded strength
and suggests improvements.
Closes #123
GitHub, GitLab, and similar platforms recognize keywords like:
Fixes #123
orfixes #123
Closes #123
orcloses #123
Resolves #123
orresolves #123
These will automatically close the referenced issue when the commit is merged to the main branch.
For commits that relate to an issue but don't resolve it:
Relates-to #123
orRelates-to #123
See #123
orsee #123
4. Include context that code can't express
The most valuable commit messages explain why a change was made, not just what changed.
Revert to synchronous API calls for checkout process
While async was theoretically more efficient, users were
experiencing confusion when redirected before confirmation
messages appeared. This led to duplicate orders and support
tickets.
This change prioritizes UX clarity over performance until
we can implement a better solution.
Thinking of your commit message audience as a decision tree can help:
Tools to improve your commit game
A few tools to help streamline the process:
# Use git commit templates
git config --global commit.template ~/.gitmessage
# In .gitmessage:
# <type>(<scope>): <subject>
#
# <body>
#
# <footer>
Or try commitizen for guided commit messages:
npm install -g commitizen
# Follow the prompts when committing
Advanced conventional commit setup:
# Install commitlint and husky for enforcement
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
# Create commitlint.config.js
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
# Set up husky hook
npx husky install
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'
This setup will validate your commit messages against the conventional commit format and prevent commits that don't follow the standard.
Commit message flow
When thinking about what to include in your commit, consider this decision flow:
The payoff: Real benefits of documentation-quality commits
- Easier code reviews: Reviewers understand the intent behind changes
- Bug hunting made simpler: Track down when and why behavior changed
- Onboarding acceleration: New team members can learn by reading the commit history
- Automated release notes: Generate meaningful changelogs directly from commits
- Your future self will thank you: Six months from now, you won't remember why you made that critical change
Key Takeaways
- Write commit messages for humans, not just for the git log
- Explain the why, not just the what
- Keep commits atomic and focused on single logical changes
- Follow conventions to enable automation and consistency
- Always include context that the code itself can't express
Conclusion
Your git history is your project's storybook. Well-documented commits create a narrative that guides developers through the codebase's evolution and reasoning. The small effort it takes to write good commit messages pays enormous dividends in team productivity and code maintainability.
Here's the playbook I'd run: set up a commit convention for your team, create templates to make it easier to follow, and gradually build the habit of writing meaningful commits. Within weeks, you'll wonder how you ever lived without this level of clarity in your codebase's history.
What's your commit message style? Have you found other patterns that work well for your team? The beauty of treating commits as documentation is that it creates a habit that continuously improves your codebase's clarity with each change you make.