A Practical Guide to Git Commit Hygiene: Crafting Meaningful Commits That Earn Trust
A Practical Guide to Git Commit Hygiene: Crafting Meaningful Commits That Earn Trust
Creating a clean, navigable Git history pays dividends long after code is shipped. Thoughtful commits make code review easier, facilitate bisecting bugs, and communicate intent to teammates. This guide walks you through a concrete workflow for commit hygiene, with concrete commands, prompts, and real-world patterns you can adopt today.
Why commit hygiene matters
- Clear history speeds up code review and onboarding.
- Smaller, well-described commits simplify debugging and revert decisions.
- A traceable narrative helps when auditing changes or rolling back features.
- Consistent structure across the team reduces cognitive load and friction. ### Define your commit philosophy
Before you touch code, agree on a lightweight standard. A simple, widely-adopted approach is:
- Each commit should represent a single logical change or fix.
- Commit messages follow a conventional format: a short, imperative summary, followed by a more detailed explanation if needed.
- No work-in-progress commits land on main/master; keep WIP in a separate branch until ready to share.
Example philosophy:
- Features: add or modify a feature without breaking existing behavior.
- Fixes: correct a bug or edge-case found in CI or testing.
- Refactors: improve structure without changing external behavior.
-
Documentation: update docs or comments.
Set up a robust branching model
Use feature branches for user stories or tasks: feature/short-description.
Protect main/master with a code review gate (pull requests) and required checks.
Use short-lived branches; target small, reviewable changes.
Keep a clean state on main: no merge commits unless they’re intentional or part of a PR workflow.
Practical setup:
- Base branch: main
- Feature branch naming: feature/issue-123-auth-fix
- Prefer rebasing locally for a tidy history, but avoid rebasing shared branches. ### Atomic commits: how to split changes
Aim for commits that do one thing, well. If a change touches multiple concerns, split it.
- Add separate commits for: code, tests, and documentation updates.
- If a change enables multiple unrelated features, split into multiple commits or even multiple branches.
Technique:
- Make a small change, commit with a descriptive message.
- Continue with the next change, commit again.
- If you realize you need to adjust the previous commit, use interactive rebase to reorder or squash.
Commands:
- Create a clean branch: git checkout -b feature/compose-hooks
- Stage only the intended changes: git add path/to/file.py
- Commit: git commit -m "Feat: add user profile page skeleton"
- Repeat for next changes. ### Crafting excellent commit messages
A well-structured message helps readers understand intent quickly.
Structure (best practice):
- Title (50-72 chars): what changed
- Body (optional): why, what problem, and any notes or references
- Footer (optional): related issues, breaking changes, or links
Pattern:
- Summary line: imperative mood, no period
- Blank line
- Detailed explanation: why this change was necessary, what it affects
- Blank line
- Fixes/Closes: references to issue tracker if applicable
Examples:
- Feat: implement login throttling to prevent brute-force attempts
- Fix: correct off-by-one error in pagination and add tests
- Refactor: extract validation logic into separate module to improve testability ### Practical commit formatting styles you can adopt
Choose one consistent style and apply it project-wide. Two popular options:
-
Conventional Commits (standardized prefixes)
- feat: a new feature
- fix: a bug fix
- refactor: code changes that neither fix a bug nor add a feature
- docs: documentation changes
- test: adding or updating tests
- chore: maintenance tasks
-
Simple imperative style (no prefix)
- Add feature X
- Fix bug in Y
- Improve Z readability
If you use Conventional Commits, ensure your CI tools or PR templates validate the format.
Writing commit messages: a worked example
Scenario: You add a new API endpoint and write tests for it, plus update corresponding docs.
- Commit 1: Add skeleton endpoint and route
- Message: Feat(api): scaffold /v1/users endpoint and routing
- Commit 2: Implement handler logic
- Message: Feat(api): implement get_user handler with input validation
- Commit 3: Add unit tests for handler
- Message: Test(api): cover get_user happy path and input errors
- Commit 4: Update API docs
- Message: Docs(api): document /v1/users response schema and error codes
- Commit 5: End-to-end smoke test changes
- Message: Test(e2e): add smoke test for user fetch path
Note how each commit is focused and explainable.
Branch hygiene and rebasing tips
- Rebase locally to clean history before pushing for review.
- Avoid rebasing public/shared branches; this can disrupt teammates.
- Use interactive rebase to squash small, related commits into a meaningful single commit:
- git rebase -i HEAD~3
- Mark commits to squash, edit messages as needed.
When collaboration is required, prefer merging PRs with a linear history option (or rebase-merge) if your team supports it.
How to handle large features gracefully
- Break the feature into milestones and create a branch per milestone.
- For each milestone, create a small, reviewable set of commits.
- Use a feature flag to merge early increments safely if you need to integrate incremental work.
Example workflow:
- Branch per milestone: feature/ab-cards-v1
- Milestone commits: skeleton, core functionality, tests, documentation, polish
-
Merge practice: open PR after each milestone, ensure checks pass
Handling tests and verification in commits
Commit tests alongside the code they exercise when feasible.
If tests fail due to a partial change, keep the failing tests out of main; use WIP branches or separate commits with clear notes.
Add tests for edge cases discovered during reviews in dedicated commits.
Tips:
- Run your test suite locally before committing: ensure green tests, minimal noise in test output.
- Use pre-commit hooks to catch common issues (linting, formatting, security checks).
Commands:
- Set up pre-commit: add to .pre-commit-config.yaml and install
- Run tests before commit: pytest -q or npm test ### Practical workflow: a day-in-the-life example
1) Start a new feature on a clean branch:
- git checkout -b feature/checkout-flow 2) Implement a small, testable change:
- Edit code
- Run tests; fix failures 3) Stage and commit incrementally:
- git add src/checkout.py
- git commit -m "Feat: checkout flow skeleton" 4) Repeat for tests and docs as separate commits 5) Rebase onto latest main to keep history tidy
- git fetch origin
- git rebase -i origin/main 6) Open a PR with a concise description and linked issue
- Ensure CI passes
-
Request specific teammates for review
Common pitfalls and how to avoid them
-
Pitfall: Large, monolithic commits
- Solution: Break into logical units; prioritize atomic commits.
-
Pitfall: Vague messages like "fix" or "misc changes"
- Solution: Use precise, descriptive messages with context in the body.
-
Pitfall: Merging without review
- Solution: Adopt PR-based workflow with required reviews and checks. ### Tooling tips to enforce good habits
Pre-commit hooks: lint, tests, and formatting at commit time.
Commitizen or conventional-commit-cli: standardize commit messages.
CI checks: run test suite, type checks, and lint on PRs.
-
Git hooks for branch protection: require PR reviews, successful checks prior to merge.
Quick-start checklist
Decide on a commit message convention (Conventional Commits or simple imperative style).
Set up a feature-branch workflow and protect main with PRs.
Write small, focused commits: one change per commit, with tests and docs as needed.
Use interactive rebase to squash or reorder commits before merging.
Enforce pre-commit checks and CI validation on PRs.
If you’d like, I can tailor this into a team-ready template: a Git workflow document with a PR checklist, sample commit messages, and a mini-guide for your project’s CI. Would you like a version optimized for a macOS/Linux dev environment, or one that emphasizes Windows tooling and PowerShell?
-
Rizwan Saleem | https://rizwansaleem.co
Top comments (0)