Commit messages document what changed and why. Good commits enable code archaeology, automated changelogs, and semantic versioning. According to research from Conventional Commits, teams using structured commit messages generate release notes automatically and maintain clearer project history. Learning to write good commit messages is fundamental git skill improving collaboration and project maintenance.
What Is Conventional Commits Format?
Conventional Commits is specification for commit messages. Format is: type(scope): description. Type indicates kind of change: feat for features, fix for bugs, docs for documentation, etc. Scope is optional indicating what part of codebase changed. Description briefly explains the change. Example: "feat(auth): add password reset via email". This structure makes commits machine-parseable enabling automation.
Common types include: feat for new features, fix for bug fixes, docs for documentation only, style for formatting changes, refactor for code restructuring, test for adding tests, chore for maintenance tasks. Some teams add types like perf for performance improvements or ci for continuous integration changes. Consistent types enable filtering commits by category and generating categorized changelogs.
Breaking changes append exclamation before colon or use BREAKING CHANGE footer. Example: "feat(api)!: remove deprecated endpoints" or body containing "BREAKING CHANGE: removed endpoints /old-api/users". Breaking changes need special visibility. Conventional Commits makes them easily identifiable for changelog generation and semantic version bumps.
How Should You Write Commit Descriptions?
Descriptions should be concise, starting with lowercase verb in imperative mood. "add user authentication" not "added authentication" or "adds authentication". Imperative mood matches git's own messages: "merge branch" not "merged branch". This consistency makes history readable. Keep description under 72 characters. Longer explanations belong in commit body, not subject line.
Focus on what and why, not how. Commit: "fix(db): prevent connection pool exhaustion during high load". Description explains problem fixed. How (implementation details) belongs in commit body if needed. Reviewers reading git log understand impact without examining code. Good descriptions make history navigable without checking every file changed.
Be specific. "fix bug" is useless. "fix crash when uploading 10MB+ images" is helpful. Specific descriptions make searching history productive. Developers grep commits finding relevant fixes. Generic descriptions make history searchable only by files changed. Specificity in 50 characters requires practice but dramatically improves commit utility.
When Should You Add Commit Bodies?
Add body for complex changes needing explanation. Body follows blank line after subject. Explain motivation for change, approach taken, and why alternatives were rejected. Body can be multiple paragraphs. No character limit. Use body telling story that code cannot: business context, architectural decisions, tradeoffs considered.
Include related issue numbers in body or footer. "Closes #123" links commit to issue and auto-closes issue on merge. "Related to #456" references related work without closing. Issue links create traceability from code changes to requirements and bug reports. Future developers understand why changes happened by following issue links.
Breaking changes need explanation in BREAKING CHANGE footer. Describe what breaks and how to migrate. Example: "BREAKING CHANGE: User.email now required. Migration: update all User.create() calls to include email parameter." This documentation helps users upgrade. Breaking changes without migration guidance frustrate users attempting to adopt new versions.
Performance improvements benefit from body explaining benchmarks. "Reduced API response time from 500ms to 50ms by implementing query caching." Numbers make improvements tangible. They also set expectations. Future changes that regress performance can be caught by comparing to documented benchmarks in commit history.
What Commit Practices Should You Follow?
Commit early and often during development. Small, focused commits are better than large mixed commits. Each commit should represent one logical change. If commit message says "and" you probably need two commits. Atomic commits make code review easier, enable selective cherry-picking, and simplify debugging through git bisect.
Never commit debugging code, commented code, or personal notes. Clean up before committing. Remove console.log statements, temporary files, and experimental code. Git history should contain only intentional changes. Noise in commits makes history hard to navigate. Code review should focus on meaningful changes, not sorting through debug artifacts.
Write commit message before making changes. Message describes intention. Code implements it. Writing message first clarifies thinking and focuses work. If you cannot write clear commit message describing change, you might not understand change well enough. Message-first approach improves code quality by forcing clear thinking upfront.
Amend commits to fix mistakes before pushing. `git commit --amend` updates last commit. Use this fixing typos in messages or adding forgotten files. Never amend commits after pushing to shared branches. Amending public commits rewrites history confusing collaborators. Fix published commits with new commits instead.
How Should You Handle Different Commit Types?
Feature commits (feat) introduce new functionality. Describe what feature adds from user perspective. "feat(dashboard): add export to PDF button" tells what users can now do. Include scope when feature is contained. Features affecting entire app can omit scope: "feat: add dark mode support". Feature commits trigger minor version bumps in semantic versioning.
Bug fix commits (fix) should reference issue numbers when available. "fix(auth): resolve token expiry race condition (#234)". Describe symptom fixed more than implementation. "fix crash when clicking submit twice" is better than "fix missing null check". Bug fixes trigger patch version bumps. Many teams automate release notes from fix commits.
Refactoring commits (refactor) should produce no behavior changes. Test suite should pass identically before and after. "refactor(utils): extract date formatting into separate module". Refactor commits without failing tests give confidence changes are safe. Breaking tests suggest refactor actually changed behavior requiring fix or feat type instead.
Documentation commits (docs) update READMEs, API docs, or inline comments. "docs(api): add examples for authentication endpoints". Documentation commits often automated out of changelogs since they do not affect code behavior. But tracking doc improvements through commits maintains documentation history.
What Tools Support Conventional Commits?
Commitizen CLI helps write properly formatted commits interactively. Running `git cz` instead of `git commit` prompts for type, scope, and description. Interactive approach prevents format mistakes. Teams new to Conventional Commits benefit from commitizen enforcing structure. It teaches format through practice.
Husky and commitlint validate commits before accepting them. Commitlint checks commits match Conventional Commits format. Invalid commits rejected with helpful error. Automated validation prevents format violations entering history. Enforcement at commit time is easier than cleaning history later. Configure validation in git hooks for automatic checking.
Standard-version or semantic-release automate versioning and changelog generation from commits. These tools parse commit history, determine next version number, and generate release notes. Feature commits bump minor version. Fixes bump patch. Breaking changes bump major version. Automation eliminates manual versioning decisions and ensures changelogs match commits.
What Common Commit Message Mistakes Should You Avoid?
Never write vague messages like "fix stuff" or "update code". Vague messages make history useless. Future developers (including you) cannot understand what changed without checking files. Every commit should be understandable from message alone. If writing clear message is hard, maybe commit is doing too much. Split into smaller, clearer commits.
Avoid inconsistent formatting mixing formats randomly. Pick convention (Conventional Commits recommended) and use it consistently. Mixed formats make automation impossible and history harder to navigate. Team needs shared format, not individual preferences. Establish convention early and enforce it through tooling.
Do not include file names in commit messages. Git tracks files automatically. "Fix bug in api/auth.js" wastes characters. "Fix authentication token validation" describes what fixed. File changes are visible in git diff. Message should explain change meaning, not enumerate files. Exception: moving or renaming files might mention filenames for clarity.
Never commit without reading diff first. `git diff --staged` shows exactly what will commit. Review ensures commit contains only intended changes. Accidentally committed credentials, large files, or unrelated changes cause problems. Taking 30 seconds reviewing before commit prevents hours debugging mysterious history problems.
Good commit messages transform git history from obscure timeline into valuable documentation. Conventional Commits provides structure enabling automation and consistency. Write clear, specific commits following conventions. Your future self and teammates will thank you every time they investigate history. Use River's tools to generate perfect commit messages following Conventional Commits standard.