Stage 2 · Tools
Branching Strategies
Commit Conventions
Standardize commit messages with Conventional Commits, automate changelogs, and enforce quality with commitlint.
Why Commit Conventions Matter
Commit messages are documentation. They explain what changed, why it changed, and what context is relevant. When every developer writes commits in a different format, git log becomes noise. When commits follow a consistent convention, they become a structured changelog, a source of truth for release notes, and a foundation for automated tooling.
Conventional Commits is a specification for adding human and machine-readable meaning to commit messages. It is the most widely adopted commit convention in the JavaScript, TypeScript, and Go ecosystems.
Structured commit messages enable automated changelogs, semantic versioning, and release notes. A tool can parse your git log and generate a changelog without human intervention — but only if every commit follows the same format.
Conventional Commits
The Conventional Commits specification defines a structured format for commit messages. Every commit message follows this pattern:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]The format has three parts: a type (required), a scope (optional), a description (required), an optional body for detailed explanation, and optional footers for references or breaking changes.
# Simple feature commit
git commit -m "feat: add user authentication"
# Feature with scope
git commit -m "feat(auth): add OAuth2 login flow"
# Bug fix
git commit -m "fix: resolve null pointer in payment processing"
# Breaking change
git commit -m "feat(api)!: change user endpoint response format
BREAKING CHANGE: The /users endpoint now returns a paginated
response instead of an array. Update client code to handle
the new response shape.
Closes #234"
# Commit with body and footer
git commit -m "docs(readme): update installation instructions
The previous instructions assumed npm was installed globally.
This update adds npx-based instructions for better onboarding.
Fixes #189"Each commit clearly communicates what changed (type + description), where it changed (scope), why it changed (body), and what it affects (footer references).
Commit Types in Depth
The commit type indicates the kind of change. Choosing the correct type is critical for automated tooling to work correctly.
| Type | Purpose | Triggers Semver | Example |
|---|---|---|---|
| feat | New feature | Minor bump | feat: add dark mode toggle |
| fix | Bug fix | Patch bump | fix: handle null response from API |
| docs | Documentation only | No bump | docs: update README setup guide |
| style | Formatting, no logic change | No bump | style: fix indentation in config |
| refactor | Code restructuring | No bump | refactor: extract auth middleware |
| perf | Performance improvement | Patch bump | perf: cache database queries |
| test | Adding or updating tests | No bump | test: add payment edge case tests |
| build | Build system or dependencies | No bump | build: upgrade webpack to v5 |
| ci | CI/CD configuration | No bump | ci: add GitHub Actions workflow |
| chore | Maintenance tasks | No bump | chore: update .gitignore |
| revert | Revert a previous commit | No bump | revert: undo auth regression |
Adding ! after the type (e.g., feat!:) indicates a breaking change. This triggers a major version bump in semantic versioning. Use it carefully — breaking changes should be rare and well-documented.
CHANGELOG Generation
When every commit follows Conventional Commits, tools can parse your git log and generate a structured CHANGELOG.md automatically. The changelog groups commits by type, links to PRs and issues, and highlights breaking changes.
# Install the CLI
npm install -g conventional-changelog-cli
# Generate changelog from git history
conventional-changelog -p angular -i CHANGELOG.md -s
# Append to existing changelog
conventional-changelog -p angular -i CHANGELOG.md -r 2The -p angular flag uses Angular's commit convention (which is compatible with Conventional Commits). The output groups commits by type and includes links to commits and PRs.
# Changelog
## [2.1.0](https://github.com/team/app/compare/v2.0.0...v2.1.0) (2024-03-15)
### Features
* **auth:** add OAuth2 login flow ([abc1234](https://github.com/team/app/commit/abc1234))
* **ui:** add dark mode toggle ([def5678](https://github.com/team/app/commit/def5678))
### Bug Fixes
* **api:** handle null response from payment provider ([ghi9012](https://github.com/team/app/commit/ghi9012))
* resolve crash on profile page load ([jkl3456](https://github.com/team/app/commit/jkl3456))
### BREAKING CHANGES
* **api:** change user endpoint response format ([mno7890](https://github.com/team/app/commit/mno7890))
The /users endpoint now returns a paginated response.The generated changelog is structured, readable, and links to every commit. It highlights breaking changes prominently and groups features and fixes separately.
Semantic Release
Semantic Release automates the entire release workflow: analyzing commits, determining the next version number, generating changelogs, publishing packages, and creating Git tags. It runs in CI and removes the human from the release process entirely.
# Install dependencies
npm install -D semantic-release @semantic-release/changelog @semantic-release/git
# Create release config
cat > .releaserc.json << 'EOF'
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/changelog", {
"changelogFile": "CHANGELOG.md"
}],
"@semantic-release/npm",
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]"
}],
"@semantic-release/github"
]
}
EOFSemantic Release analyzes every commit since the last tag. If it finds a feat commit, it bumps the minor version. If it finds a fix, it bumps the patch. Breaking changes trigger a major bump.
Run semantic-release in a GitHub Actions workflow triggered on push to main. It will automatically create releases, update changelogs, and publish packages on every merge.
Commitlint Setup
Commitlint validates commit messages against the Conventional Commits specification. It runs as a Git hook and rejects commits that do not follow the format.
# Install commitlint and husky
npm install -D @commitlint/cli @commitlint/config-conventional husky
# Initialize husky
npx husky init
# Create commitlint config
cat > commitlint.config.js << 'EOF'
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"feat", "fix", "docs", "style", "refactor",
"perf", "test", "build", "ci", "chore", "revert",
],
],
"scope-case": [2, "always", "lower-case"],
"subject-case": [2, "never", ["start-case", "pascal-case"]],
"header-max-length": [2, "always", 100],
},
};
EOF
# Add the commit-msg hook
echo "npx --no -- commitlint --edit $1" > .husky/commit-msgThe hook runs on every git commit. If the commit message does not match the format, the commit is rejected. The config enforces allowed types, case conventions, and header length.
# This commit will be rejected
git commit -m "fixed stuff"
# ❌ subject may not be empty [subject-empty]
# ❌ type may not be empty [type-empty]
# This commit will be accepted
git commit -m "fix: resolve null pointer in auth module"
# ✅ All good!Commitlint gives immediate feedback. Developers learn the convention by fixing rejected messages. Over time, the format becomes second nature.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.