Git Commit Standards
Structuring commits and branches so history tells a legible story.
Overview
Git workflow standards cover branching strategy, commit message format, PR size, and merge strategy. Consistent conventions make the git log a reliable audit trail and enable automation (changelogs, semantic versioning, CI triggers). Conventional Commits, GitHub Flow, and trunk-based development are the most widely adopted standards.
Origin
Vincent Driessen published Git Flow in 2010 as a branching model for release-based software. GitHub Flow (2011) simplified it to a single main branch + feature branches for continuously-deployed software. Conventional Commits specification (2019) formalised structured commit messages inspired by the Angular commit message convention (2012).
Examples
Conventional Commits with commitlint enforcement
// commitlint.config.ts
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert', 'ci'],
],
'scope-case': [2, 'always', 'lower-case'],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100],
},
};
// Valid commit messages:
// feat(orders): add bulk cancellation endpoint
// fix(auth): prevent token refresh loop on 401
// docs(api): update rate limiting documentation
// refactor(payments): extract Stripe client to separate module
// perf(db): add index on orders.customer_id
// chore(deps): upgrade Stripe SDK to 14.x
// .husky/commit-msg:
// npx --no -- commitlint --edit ${1}Conventional Commits enables semantic-release to automate version bumps: feat bumps minor, fix bumps patch, a BREAKING CHANGE footer bumps major. This eliminates manual versioning decisions and keeps CHANGELOG.md accurate.
Git aliases and workflow helpers in shell config
# ~/.gitconfig useful aliases for daily workflow
# [alias]
# st = status --short --branch
# lg = log --oneline --decorate --graph --all
# recent = branch --sort=-committerdate --format='%(committerdate:relative)%09%(refname:short)' | head -10
# wip = !git add -A && git commit -m 'wip: checkpoint'
# unwip = reset HEAD~1
# pr = !gh pr create --fill
# sync = !git fetch origin && git rebase origin/main
# cleanup = !git fetch --prune && git branch --merged | grep -v main | xargs git branch -d
# Trunk-based development flow:
# 1. git sync (rebase local main onto origin/main)
# 2. git checkout -b feat/add-bulk-cancel
# 3. ... commits ...
# 4. git sync (rebase branch onto latest main before PR)
# 5. git pr (gh pr create --fill opens PR)
# 6. After merge: git checkout main && git sync && git cleanupgit sync uses rebase onto origin/main rather than merge to keep the branch history linear. This avoids merge commits in feature branches and produces a clean main history after squash-merge.
Use Cases
- 01Automated changelog generation from Conventional Commits using standard-version or semantic-release
- 02CI pipeline triggers based on branch naming (feat/*, fix/*, hotfix/*) to apply different pipeline configurations
- 03Blame tracking: descriptive commit messages make git blame and git log --follow useful for tracing why a line was changed
- 04Code review: small, focused commits make PR reviews faster; a commit that does one thing is reviewable in minutes rather than hours
When Not to Use
- //Do not enforce strict conventional commits for very small teams or personal projects where the overhead outweighs the automation benefit
- //Do not squash all commits into one before merging if the individual commit history carries important context; squash-merge only when the branch commits are noisy WIP checkpoints
- //Do not use long-lived feature branches (weeks+) in trunk-based development projects; use feature flags to hide incomplete work on main instead
Technical Notes
- Git's object model stores snapshots, not diffs: each commit points to a tree object representing the full file state at that commit. Diff views are computed on the fly from snapshot pairs
- Rebase rewrites commit SHAs; never rebase commits already pushed to a shared remote branch unless the team has agreed and all collaborators know to reset. Force-push to main is catastrophic without branch protection
- Branch protection rules (GitHub: Settings > Branches) should require: PR review approval, status checks passing, and no force pushes to main. These are the minimum guardrails for a team codebase
- git bisect uses binary search to find the commit that introduced a bug; it requires a reliable test (or manual repro) and works best when commits are small and atomic, reinforcing the single-responsibility principle for commits