Skip to main content

Git Workflows That Keep Your Team Sane

February 18, 2026

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 workflow

Make 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-reference

Keep 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 quickly

Delete 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 --prune

Collaboration

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 true

Use 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 review

Review 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 --abort

History 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~3
pick abc1234 feat: add user model
squash def5678 fix typo in user model
squash ghi9012 add missing field to user model

Use 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 reset

Avoid 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-branch

Configuration

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 zdiff3

Useful 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 commit

Security

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 .env

Sign 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.git

Useful 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 abc1234

Inspect 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" --oneline

Cherry-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..def5678

Quick Reference

PracticeWhy
Small, atomic commitsEasier to review, revert, and bisect
Conventional commit messagesConsistent, parseable history
Branch per feature/fixIsolate work, enable parallel development
Pull with rebaseLinear, clean history
Never force push shared branchesPrevents overwriting others' work
--force-with-lease over --forceSafer force push
Delete merged branchesKeep branch list clean
Sign commitsVerify authorship
.gitignore from day oneNo generated files or secrets in repo
git stash for WIPQuick context switching

Summary

Git best practices come down to:

  1. Commit well — small, atomic commits with meaningful messages
  2. Branch smartly — short-lived feature branches, merge via PR
  3. Collaborate safely — pull before push, never force push main
  4. Keep history clean — interactive rebase, delete merged branches
  5. Stay secure — never commit secrets, sign commits, use SSH

Recommended Posts