Commit Messages and Conventional Commits
Commit Messages and Conventional Commits
Why it matters
Clear commit messages communicate intent and make history searchable. They help reviewers, enable reliable rollbacks, and allow automation (changelogs, release tooling) to work predictably.
The Conventional Commit format (summary)
<type>(optional scope): <short summary>
Common types: feat, fix, docs, chore, refactor, test, perf, build, ci
Expanded guide
Overview
A commit message is a log of your changes. Clear, consistent commit messages communicate the why behind a change, not just the what. The Conventional Commits specification provides a simple, machine-readable format that works well with automated releases and changelog generation.
Format (detailed)
<type>[optional scope]: <short summary>
[optional body]
[optional footer(s)]
- type: one of the conventional types (feat, fix, docs, chore, etc.)
- scope: optional, a noun describing the affected area (e.g., auth, api)
- short summary: imperative, ~50 chars or less
- body: use to explain the motivation, key details, and trade-offs
- footer: reference issues (e.g.,
Fixes #123) or mark breaking changes (BREAKING CHANGE:)
Examples
- Simple:
feat(auth): add Google OAuth login
- With scope and short description:
fix(api): handle 404 error for missing project
- With body and footer:
feat(api): implement rate limiting for public endpoints
Prevent abuse by limiting requests to 100/min per IP. Uses
express-rate-limit and adds configuration to the server startup
to allow overrides in staging/production.
Fixes #42
- Breaking change example:
feat(auth): upgrade token format
BREAKING CHANGE: token format switched to JWTv2 which is not
backward-compatible with JWTv1. All clients must rotate tokens.
Commit message style tips
- Use imperative mood in the subject line ("add", not "added" or "adds").
- Keep the subject under ~50 characters and wrap the body at ~72 characters.
- Use the body to explain why the change was made and any non-obvious consequences.
- Avoid including stack traces or long diffs in commit messages; prefer linking to issues.
- Keep commits focused and small — one logical change per commit.
Tooling & automation
- Use linters like
commitlintto enforce Conventional Commits in CI. - Use git hooks (
husky,pre-commit) to run linters or prompt for message format locally. - Many CI/CD tools can consume Conventional Commit messages to auto-generate changelogs and decide semantic version bumps.
Practical workflows
- For feature branches, write clear commit messages for each logical step. When ready, squash or rebase locally if you want a tidy history before opening a PR.
- For collaborative branches, prefer smaller commits and avoid rewriting history once pushed.
Useful Links
1. Why Structure Matters
| Benefit | Impact |
|---|---|
| Greppable history | Quickly locate introduction of features/bugs |
| Automated changelogs | Release notes generation (semantic-release) |
| Easier debugging | Bisecting & reasoning about intent |
| Semantic versioning | Map commit types to version bumps |
2. Type Reference Table
| Type | Meaning | Semantic Version Impact (Typical) |
|---|---|---|
| feat | New feature | MINOR |
| fix | Bug fix | PATCH |
| docs | Docs only | None |
| chore | Maintenance / tooling | None |
| refactor | Internal change no behavior diff | None |
| perf | Performance improvement | PATCH (or MINOR if notable) |
| test | Add/modify tests | None |
| ci | CI pipeline changes | None |
| build | Build system / deps | None (unless breaking) |
| BREAKING CHANGE | Backwards incompatible | MAJOR |
3. Bad vs Good Subjects
| Bad | Issue | Improved |
|---|---|---|
| "fix stuff" | Vague | fix(api): handle null user id |
| "update" | No context | chore(deps): bump fastapi 0.103→0.104 |
| "adding login form" | Not imperative | feat(auth): add login form |
| "Refactored code" | Lacks scope | refactor(cart): extract price calculator |
4. Helpful Body Guidance
Answer these in the body (if not trivial):
1. Motivation (why now?)
2. Approach (key decisions)
3. Alternatives considered (if relevant)
4. Risks / follow-ups
5. Issue links / docs references
Format: wrap lines ~72 chars; blank lines between paragraphs; bullet points for multi-part rationale.
5. Breaking Change Footer Example
feat(api): change /projects response shape
BREAKING CHANGE: remove legacy field `owner_name`; clients must
use nested `owner.name`.
6. Automation Matrix
| Goal | Tool | Example |
|---|---|---|
| Enforce format | commitlint + Husky | Pre-commit hook rejects bad subject |
| Auto version + changelog | semantic-release | Bumps version & publishes notes |
| Template assistance | Git commit template | ~/.gitmessage prefilled sections |
| Sign authenticity | GPG / SSH Signing | git config commit.gpgsign true |
7. Interactive Rebase Polish (Local Only)
git fetch origin
git rebase -i origin/main
# squash/reword as needed
git push --force-with-lease
8. Quick Checklist
- Subject < 50 chars, imperative
- Conventional type present & correct
- Scope added if clarifying
- Body explains WHY for non-trivial change
- BREAKING CHANGE footer if incompatible
- Issues referenced (Fixes #ID) when applicable
- No secrets / huge stack traces