Commit Hygiene
Write History Well
Why it matters
Commits are the documentation of your project’s evolution. Good commit hygiene makes debugging, reviewing, and understanding code dramatically easier.
Key concepts
- Atomic Commits — Smallest complete change that makes sense alone
- Commit Messages — Documentation of what and why you changed
- .gitignore — File specifying intentionally untracked files
- Conventional Commits — Standardized commit message format
The idea
The Book Chapter Analogy
Each commit should be like a book chapter:
- Focused: One topic per chapter
- Complete: Doesn’t leave the reader hanging
- Titled well: You can understand the content from the title
The Atomic Commit
An atomic commit is the smallest change that makes sense on its own.
Bad:
"Update files"
- Fix login bug
- Add new feature
- Update README
- Refactor database
Good:
"Fix null pointer in login handler"
"Add password reset feature"
"Update README with new endpoints"
"Refactor database connection pooling"
Each can be reverted, cherry-picked, or understood independently.
Walkthrough
The Anatomy of a Good Commit Message
feat: add password reset functionality
Implement password reset flow with email verification.
Users can now request a password reset link that expires
after 24 hours.
- Add PasswordResetController
- Create email template
- Add expiration check middleware
Closes #234
Structure:
- Subject line: 50 chars max, imperative mood
- Blank line
- Body: Explain WHY, not what (the diff shows what)
- Footer: References, breaking changes
Conventional Commits
feat: add user authentication
fix: resolve race condition in queue
docs: update API documentation
style: format code with prettier
refactor: extract validation logic
test: add unit tests for auth
chore: update dependencies
perf: optimize database queries
What to Commit
| ✅ Commit | ❌ Don’t Commit |
|---|---|
| Source code | Build artifacts (dist/, target/) |
| Config files | Dependencies (node_modules/) |
| Documentation | Secrets (.env, credentials) |
| Tests | IDE settings (.idea/, .vscode/) |
| .gitignore | Large binaries |
The .gitignore File
# Dependencies
node_modules/
vendor/
# Build artifacts
dist/
target/
*.pyc
__pycache__/
# Environment
.env
.env.local
*.pem
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
Key takeaways
- One logical change per commit
- Imperative mood in subject line
- Explain WHY in the body
- Never commit secrets or build artifacts
Dos & don’ts
✅ DO
- Write meaningful subjects: “Fix auth timeout” not “fix bug”
- Use imperative mood: “Add” not “Added” or “Adding”
- Keep subject under 50 chars: It’s a summary
- Explain the WHY: The diff shows the what
- Reference issues:
Refs #42orCloses #42
❌ DON’T
- Don’t commit secrets: API keys, passwords, tokens
- Don’t commit generated files: node_modules, dist, *.pyc
- Don’t use vague messages: “WIP”, “fix”, “update”
- Don’t mix concerns: “Fix bug and add feature” = 2 commits
- Don’t commit broken code to main: Use branches
Going deeper
Signed commits:
git commit -S signs your commit with your GPG key.
Proves you actually made the commit. GitHub shows “Verified” badge.
Commit hooks: Pre-commit hooks can enforce formatting, run tests, check for secrets. Tools: Husky (JS), pre-commit (Python), lefthook (Go).
Common mistakes
Committing secrets:
Even if you remove them in the next commit, they’re in history forever.
Use tools like git-secrets or gitleaks to prevent this.
Mega-commits: “Implement entire feature” commits are hard to review and revert. Break work into logical steps, commit each step.