Styling Guides

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 cleanup

git 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