Git is the backbone of modern software development, but using it effectively goes beyond git add and git push. Here are the best practices that will make you a better collaborator and keep your project history clean.
Commit Best Practices
Write Meaningful Commit Messages
A good commit message explains why, not just what:
# Bad
git commit -m "fix"
git commit -m "update code"
git commit -m "changes"
# Good
git commit -m "Fix login redirect loop for expired sessions"
git commit -m "Add rate limiting to /api/auth endpoint"
git commit -m "Refactor user service to use dependency injection"Follow Conventional Commits
Use a consistent format for commit messages:
<type>(<scope>): <description>
[optional body]
[optional footer]
Common types:
feat(auth): add OAuth2 login with Google
fix(cart): resolve race condition on quantity update
docs(readme): add deployment instructions
style(ui): fix button alignment on mobile
refactor(api): extract validation middleware
test(users): add integration tests for user creation
chore(deps): update React to v19
perf(search): add database index for full-text search
ci(actions): add automated deployment workflowMake Small, Atomic Commits
Each commit should represent one logical change:
# Bad - one giant commit
git add .
git commit -m "Add user feature, fix bugs, update styles"
# Good - separate concerns
git add src/models/user.ts src/services/user.service.ts
git commit -m "feat(users): add user model and service"
git add src/routes/user.routes.ts
git commit -m "feat(users): add user API endpoints"
git add src/styles/profile.css
git commit -m "style(users): add profile page styles"Don't Commit Generated Files
# .gitignore
node_modules/
dist/
build/
.next/
__pycache__/
*.pyc
.env
.env.*
*.log
.DS_Store
coverage/Branching
Use a Branching Strategy
GitHub Flow (simple, great for most teams):
main ──────────────────────────────────────►
│ │
└── feature/add-auth ──── PR ──┘
│ │
└── fix/login-bug ──────── PR ──┘Git Flow (for projects with scheduled releases):
main ──────────────────────────────────────►
│ │
develop ──────────────────────────────────►
│ │ │
└── feature/auth ────┘ │
│ │
└── release/1.2 ──────────────┘Name Branches Descriptively
# Bad
git checkout -b patch1
git checkout -b johns-branch
git checkout -b temp
# Good
git checkout -b feature/user-authentication
git checkout -b fix/cart-total-calculation
git checkout -b refactor/database-connection-pool
git checkout -b docs/api-endpoint-referenceKeep Branches Short-Lived
Long-lived feature branches lead to painful merges. Aim to merge within a few days:
# Create branch, make changes, push, open PR
git checkout -b feature/add-search
# ... make focused changes ...
git push -u origin feature/add-search
# Open PR immediately, get reviewed, merge quicklyDelete Merged Branches
# Delete local branch after merge
git branch -d feature/add-search
# Delete remote branch
git push origin --delete feature/add-search
# Clean up stale remote tracking branches
git fetch --pruneCollaboration
Pull Before You Push
# Always pull latest changes before pushing
git pull --rebase origin main
# Or set it as default
git config --global pull.rebase trueUse Pull Requests for Code Review
Never push directly to main. Always go through a PR:
# Create feature branch
git checkout -b feature/new-endpoint
# Make changes and push
git add .
git commit -m "feat(api): add user search endpoint"
git push -u origin feature/new-endpoint
# Open PR on GitHub/GitLab for reviewReview PRs Thoroughly
A good PR should have:
- Clear title — summarizes the change
- Description — explains why, not just what
- Small scope — easier to review, fewer bugs
- Tests — proves the change works
- Screenshots — for UI changes
Resolve Conflicts Carefully
# Update your branch with latest main
git checkout feature/my-branch
git fetch origin
git rebase origin/main
# If conflicts arise, resolve them file by file
# Then continue the rebase
git add .
git rebase --continue
# If things go wrong, abort and start over
git rebase --abortHistory Management
Use Interactive Rebase to Clean Up
Before opening a PR, clean up your commit history:
# Squash last 3 commits into one
git rebase -i HEAD~3pick abc1234 feat: add user model
squash def5678 fix typo in user model
squash ghi9012 add missing field to user modelUse git stash for Work in Progress
# Save current work without committing
git stash
# Do something else (switch branches, pull changes, etc.)
git checkout main
git pull
# Come back and restore your work
git checkout feature/my-branch
git stash pop
# Stash with a description
git stash push -m "WIP: user validation logic"
# List all stashes
git stash list
# Apply a specific stash
git stash apply stash@{2}Use git bisect to Find Bugs
# Start bisect
git bisect start
# Mark current commit as bad
git bisect bad
# Mark a known good commit
git bisect good v1.0.0
# Git will checkout commits for you to test
# Mark each as good or bad
git bisect good # or git bisect bad
# When found, reset
git bisect resetAvoid Rewriting Public History
# NEVER force push to shared branches
git push --force origin main # DON'T DO THIS
# If you must force push your own branch, use --force-with-lease
# It fails if someone else pushed changes you haven't fetched
git push --force-with-lease origin feature/my-branchConfiguration
Set Up Your Identity
git config --global user.name "Your Name"
git config --global user.email "your@email.com"Useful Global Config
# Default branch name
git config --global init.defaultBranch main
# Rebase on pull instead of merge
git config --global pull.rebase true
# Better diff algorithm
git config --global diff.algorithm histogram
# Auto-correct typos (runs after 1.5s)
git config --global help.autocorrect 15
# Prune on fetch
git config --global fetch.prune true
# Better merge conflict markers
git config --global merge.conflictstyle zdiff3Useful Aliases
git config --global alias.st "status -sb"
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.cm "commit -m"
git config --global alias.co "checkout"
git config --global alias.br "branch"
git config --global alias.undo "reset --soft HEAD~1"
git config --global alias.amend "commit --amend --no-edit"
git config --global alias.wip "stash push -m"# Usage
git st # Short status
git lg # Pretty log graph
git cm "msg" # Quick commit
git undo # Undo last commit (keep changes)
git amend # Add to last commitSecurity
Never Commit Secrets
# If you accidentally committed a secret
# 1. Remove it from the file
# 2. Rotate the secret immediately
# 3. Remove from history
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Or use BFG Repo Cleaner (faster)
bfg --delete-files .envSign Your Commits
# Set up GPG signing
git config --global commit.gpgsign true
git config --global user.signingkey YOUR_GPG_KEY_ID
# Signed commits show as "Verified" on GitHub
git commit -S -m "feat: add verified feature"Use SSH Over HTTPS
# Generate SSH key
ssh-keygen -t ed25519 -C "your@email.com"
# Add to SSH agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Test connection
ssh -T git@github.com
# Clone with SSH
git clone git@github.com:user/repo.gitUseful Commands
Undo Common Mistakes
# Undo last commit (keep changes staged)
git reset --soft HEAD~1
# Undo last commit (keep changes unstaged)
git reset HEAD~1
# Discard all uncommitted changes
git checkout -- .
# Undo a specific file change
git checkout -- path/to/file.ts
# Undo a pushed commit (creates new revert commit)
git revert abc1234
# Recover deleted branch or lost commits
git reflog
git checkout -b recovered-branch abc1234Inspect Changes
# See what changed in a file
git log -p path/to/file.ts
# See who changed each line
git blame path/to/file.ts
# See changes between branches
git diff main..feature/my-branch
# See files changed between branches
git diff --name-only main..feature/my-branch
# Search commit messages
git log --grep="fix login"
# Search code changes
git log -S "functionName" --onelineCherry-Pick Specific Commits
# Apply a specific commit to current branch
git cherry-pick abc1234
# Cherry-pick without committing (stage only)
git cherry-pick --no-commit abc1234
# Cherry-pick a range
git cherry-pick abc1234..def5678Quick Reference
| Practice | Why |
|---|---|
| Small, atomic commits | Easier to review, revert, and bisect |
| Conventional commit messages | Consistent, parseable history |
| Branch per feature/fix | Isolate work, enable parallel development |
| Pull with rebase | Linear, clean history |
| Never force push shared branches | Prevents overwriting others' work |
--force-with-lease over --force | Safer force push |
| Delete merged branches | Keep branch list clean |
| Sign commits | Verify authorship |
.gitignore from day one | No generated files or secrets in repo |
git stash for WIP | Quick context switching |
Summary
Git best practices come down to:
- Commit well — small, atomic commits with meaningful messages
- Branch smartly — short-lived feature branches, merge via PR
- Collaborate safely — pull before push, never force push
main - Keep history clean — interactive rebase, delete merged branches
- Stay secure — never commit secrets, sign commits, use SSH