Keeping History Clean
A clean Git history makes debugging, code review, and project maintenance much easier. This lesson covers techniques for maintaining a readable, useful commit history.
Why Clean History Matters
Compare these histories:
# Messy history
abc123 fix
def456 wip
789abc oops
fedcba fix again
123abc finally works
# Clean history
abc123 Add user authentication
def456 Implement password reset flow
789abc Add email verification
fedcba Refactor auth module for better testing
Clean history helps with:
- Debugging (using
git bisect) - Code review
- Understanding changes
- Generating changelogs
- Reverting specific changes
Atomic Commits
Each commit should be:
- Complete: The code works after this commit
- Single-purpose: One logical change
- Self-contained: Understandable on its own
Bad: Mixed Concerns
git add .
git commit -m "Fix bug, add feature, update docs, refactor"
Good: Atomic Commits
git add auth.js
git commit -m "Fix null check in authentication"
git add signup.js
git commit -m "Add email validation to signup"
git add README.md
git commit -m "Update authentication documentation"
Avoiding WIP Commits
Don't push work-in-progress commits:
Problem
abc123 WIP
def456 more WIP
789abc almost done
fedcba fix
Solution 1: Amend
# First commit
git commit -m "WIP"
# Continue working, then amend
git add .
git commit --amend -m "Add user login feature"
Solution 2: Interactive Rebase
Before pushing, squash WIP commits:
git rebase -i HEAD~4
pick abc123 Start login feature
squash def456 WIP
squash 789abc more WIP
squash fedcba final touches
Result: One clean commit.
Rewriting History (Before Push)
Amend Last Commit
# Fix message
git commit --amend -m "Better message"
# Add forgotten file
git add forgotten.js
git commit --amend --no-edit
Interactive Rebase
git rebase -i HEAD~5
You can:
- Squash: Combine commits
- Reword: Change messages
- Reorder: Change order
- Drop: Remove commits
- Edit: Modify content
Autosquash Fixups
# Create fixup commit
git commit --fixup abc123
# Autosquash during rebase
git rebase -i --autosquash main
Keeping Branches Up to Date
Merge vs Rebase
# Merge (creates merge commits)
git checkout feature
git merge main
# Rebase (linear history)
git checkout feature
git rebase main
When to Merge
- Preserving feature branch history
- Shared branches
- After feature is complete
When to Rebase
- Keeping feature branch updated
- Before PR review
- Personal/local branches
Squash Merging
When merging PRs, squash all commits into one:
Feature branch:
abc123 Start feature
def456 Add validation
789abc Fix edge case
fedcba Add tests
After squash merge to main:
xyz789 Add user authentication feature (#123)
Configure in GitHub
Settings → General → "Squash and merge" as default
Commit Hooks
Enforce standards with Git hooks:
Pre-Commit
#!/bin/sh
# .git/hooks/pre-commit
# Run linting
npm run lint
if [ $? -ne 0 ]; then
echo "Linting failed. Fix errors before committing."
exit 1
fi
Commit-Msg
#!/bin/sh
# .git/hooks/commit-msg
# Validate commit message format
if ! grep -qE "^(feat|fix|docs|style|refactor|test|chore): .+" "$1"; then
echo "Invalid commit message format"
echo "Use: type: description"
exit 1
fi
With Husky
// package.json
{
"husky": {
"hooks": {
"pre-commit": "npm run lint",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
Cleaning Up Branches
Delete Merged Branches
# List merged branches
git branch --merged main
# Delete merged (except main)
git branch --merged main | grep -v main | xargs git branch -d
# Delete remote merged branches
git fetch --prune
Regular Cleanup
# See stale remote-tracking branches
git remote prune origin --dry-run
# Prune them
git remote prune origin
Commit Message Consistency
Team Template
# Set up template
git config --local commit.template .gitmessage
Conventional Commits
<type>(<scope>): <description>
[body]
[footer]
Commitlint
# Install
npm install --save-dev @commitlint/cli @commitlint/config-conventional
# Configure
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
Revert vs Reset
For Pushed Commits
Use revert (creates new commit):
git revert abc123
# Creates: "Revert 'Original message'"
For Unpushed Commits
Use reset:
git reset --soft HEAD~1 # Keep changes staged
git reset --mixed HEAD~1 # Keep changes unstaged
git reset --hard HEAD~1 # Discard changes
Best Practices Summary
Before Committing
- Self-review: Check your changes
- Stage selectively: Only related changes
- Write good message: Clear, imperative, descriptive
Before Pushing
- Clean up WIP: Squash/reword if needed
- Rebase on main: Ensure up to date
- Run tests: Everything should pass
Branch Management
- Delete merged branches: Keep list clean
- Prune regularly: Remove stale references
- Follow naming: Consistent conventions
Team Workflow
- Agree on conventions: Document standards
- Use tools: Hooks, linting
- Review history: Part of PR review
Summary
- Make atomic commits (one purpose each)
- Avoid pushing WIP commits
- Use interactive rebase to clean up before pushing
- Squash when merging features
- Use commit hooks to enforce standards
- Delete merged branches
- Prefer revert for pushed commits, reset for unpushed
- Document and follow team conventions
Congratulations! You've completed the Git & GitHub Mastery course!

