Git Commit Messages
Write clearer Git commit messages using proven rules, Conventional Commits, and tools that support changelogs and releases.
Why Good Commit Messages Matter
A commit message is part of your project's long-term memory. It explains why a change exists and helps later readers understand what happened without opening every file diff first.
Good messages improve:
- history review
git blameinvestigations- changelog generation
- release notes
- team communication
A weak message like fix stuff quickly becomes useless. A stronger message like fix(auth): handle expired refresh tokens gives useful context immediately.
Tip: Write commit messages for the person reading them months later, not just for the moment you type them.
The 7 Rules of a Great Commit Message
A widely used set of guidelines includes these rules:
- Separate subject from body with a blank line
- Keep the subject around 50 characters
- Capitalize the subject line
- Do not end the subject with a period
- Use the imperative mood
- Wrap the body near 72 characters
- Explain what and why in the body
Example:
Add validation for release tag names
Reject tag names that do not match the release policy. This prevents
invalid CI/CD releases from being published accidentally.
Imperative Mood
Subjects like Add, Fix, and Refactor read naturally after the phrase "If applied, this commit will...".
Good vs Bad Messages
Bad examples:
stuff
misc fixes
updated files
Better examples:
docs: clarify GitHub workflow steps
fix(ci): restore cache key for Node 22 builds
feat(api): add pagination to audit log endpoint
The better versions are specific and searchable.
Conventional Commits
Conventional Commits define a standard format that both people and tooling can understand.
Basic pattern:
type(scope): description
Common types include:
feat:fix:docs:chore:refactor:test:build:ci:
Examples:
feat(auth): add SSO login flow
fix(ui): prevent navbar overlap on mobile
docs(git): add submodule troubleshooting section
Breaking Changes
A breaking change can be marked with ! or with a BREAKING CHANGE: note in the body.
feat(api)!: rename userId field to accountId
BREAKING CHANGE: Clients must send accountId instead of userId.
Note: Conventional Commits are most useful when the whole team follows the same format and a hook or CI job validates it.
Conventional Commits and Semantic Versioning
Conventional Commits connect well to semantic versioning:
- MAJOR for breaking changes
- MINOR for backward-compatible features
- PATCH for backward-compatible fixes
A common mapping is:
feat:→ minor bumpfix:→ patch bumpBREAKING CHANGE:or!→ major bump
That makes commit history useful for release automation.
Helpful Tools
commitlint
commitlint checks whether commit messages follow your rules.
npm install --save-dev @commitlint/cli @commitlint/config-conventional
Example config:
cat > commitlint.config.js <<'EOF'
module.exports = { extends: ['@commitlint/config-conventional'] };
EOF
Test a message:
echo "fix: correct cache key" | npx commitlint
Teams often run this from a commit-msg hook.
semantic-release
semantic-release reads structured commit history and automates versioning and release notes.
npm install --save-dev semantic-release
A common pattern is:
- developers write Conventional Commits
commitlintvalidates them- CI runs
semantic-releaseonmain - versioning and changelogs are generated automatically
What You Should Remember
Good commit messages make history easier to search, review, debug, and release. The seven classic rules improve clarity, while Conventional Commits provide a machine-readable format that works well with tools like commitlint and semantic-release. Better commit messages are a small habit with long-term payoff.
Test Your Understanding
Exercise 1: Why commit messages matter
Which is the best reason strong commit messages improve long-term project maintenance?
Exercise 2: Recognizing a Conventional Commit
Which option follows the Conventional Commits pattern from the lesson?
Exercise 3: Linking commits to version bumps
According to the lesson, which type of change most directly signals a MAJOR semantic version bump?