The Git Workflow That Saved My Sanity
After years of trial and error, this is the workflow I use for every project.
The Problem
❌ Bad workflow:
- Everything on main branch
- Commit messages like "fix", "update", "stuff"
- No code review, just push to production
- Merge conflicts every day
- "Which version has the bug fix?" — nobody knows
✅ Good workflow:
- Feature branches with clear naming
- Descriptive commit messages
- PRs with code review before merge
- Clean git history that tells a story
Branch Naming Convention
# Format: type/ticket-id-or-description
feature/user-authentication # New feature
fix/login-redirect-bug # Bug fix
hotfix/payment-expiry # Production hotfix
refactor/user-service # Code refactor (no behavior change)
docs/api-endpoints # Documentation only
test/payment-flow # Tests only
chore/update-dependencies # Maintenance tasks
release/v1.2.0 # Version release
Commit Message Convention (Conventional Commits)
type(scope): subject
body (optional)
footer (optional)
Types
| Type | When to Use |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation changes |
style |
Formatting, semicolons, etc. (no logic change) |
refactor |
Code restructuring (no behavior change) |
perf |
Performance improvement |
test |
Adding or updating tests |
chore |
Build process, dependencies, tooling |
ci |
CI/CD configuration |
revert |
Revert previous commit |
Examples
git commit -m "feat(auth): add OAuth2 Google login"
git commit -m "fix(cart): resolve race condition in add-to-cart"
git commit -m "perf(api): cache user profile queries (3x faster)"
git commit -m "docs(readme): update installation instructions for Node 22"
git commit -m "refactor(user): extract validation into separate module"
git commit -m "test(payment): add unit tests for Stripe webhook handler"
git commit -m "chore(deps): upgrade express from 4.18 to 4.21"
git commit -m "ci(github): add Node.js 22 to test matrix"
# Breaking change (!)
git commit -m "feat(api)!: remove deprecated /v1 endpoints"
# Scope can be omitted if not applicable
git commit -m "chore: update .gitignore"
My Daily Workflow
# 1. Start new work
git checkout main
git pull origin main
git checkout -b feature/search-filters
# ... write code ...
# 2. Stage and commit (review your changes first!)
git diff # See what changed
git status # See what's staged/unstaged
git add -p # Interactive staging (review each hunk!)
git commit -m "feat(search): add category and price filters"
# ... write more code ...
# 3. Keep commits atomic (one logical change per commit)
git add src/components/SearchFilters.tsx
git commit -m "feat(search): add SearchFilters component"
git add src/api/search.ts
git commit -m "feat(search): implement filter API endpoint"
# 4. Sync with main before pushing
git fetch origin
git rebase origin/main # Not merge! Keeps history clean
# 5. Push and create PR
git push -u origin feature/search-filters
gh pr create --title "Add search filters" --body "## Summary\n..."
# 6. After review + merge, clean up
git checkout main
git pull origin main
git branch -d feature/search-filters # Delete local branch
git push origin --delete feature/search-filters # Delete remote branch
The Golden Rules
Rule 1: Atomic Commits
# ❌ One giant commit with everything
git add .
git commit -m "did some stuff"
# ✅ Small, focused commits
git add auth/login.ts
git commit -m "feat(auth): implement login form validation"
git add auth/session.ts
git commit -m "feat(auth): add session management with JWT"
git add tests/auth.test.ts
git commit -m "test(auth): add login and session tests"
Why? Easy to revert one change without undoing others. Easy to bisect bugs.
Rule 2: Write Good Messages
# ❌ Useless messages
"updates"
"fix bug"
"wip"
"asdf"
"minor changes"
# ✅ Informative messages
"fix(auth): handle expired token gracefully by redirecting to login"
"feat(dashboard): show revenue chart for last 30 days"
"refactor(api): extract pagination logic into reusable utility"
Why? Your future self (and teammates) will thank you when debugging.
Rule 3: Don't Break Main
# ❌ Push directly to main
git checkout main
git commit -m "hotfix"
git push origin main # DANGEROUS!
# ✅ Always use branches + PRs
git checkout -b fix/critical-bug
# ... fix ...
git push origin fix/critical-bug
# Create PR → get review → merge to main
Rule 4: Clean Up Branches
# List merged branches (safe to delete)
git branch --merged main
# Delete merged local branches
git branch --merged main | grep -v '^\*\|main' | xargs git branch -d
# Force delete unmerged branch (use carefully)
git branch -D abandoned-feature
Handling Common Situations
"I committed to the wrong branch"
# Undo last commit (keep changes staged)
git reset --soft HEAD~1
git stash
git checkout correct-branch
git stash pop
"I need to split a commit"
# Interactive rebase of last 3 commits
git rebase -i HEAD~3
# Change 'pick' to 'edit' on the commit you want to split
# Then:
git reset HEAD~1 # Unstage but keep changes
git add file1.ts # Stage part 1
git commit -m "part 1"
git add file2.ts # Stage part 2
git commit -m "part 2"
git rebase --continue
"Merge conflict hell"
# Don't panic! Follow these steps:
# 1. Understand what changed
git diff --name-only # Which files have conflicts?
git diff file.ts # See the actual conflict markers
# 2. Resolve each conflict
# Open file → find <<<<<<< ======= >>>>>>> markers
# Choose ours, theirs, or manual edit
# 3. Mark as resolved
git add file.ts # Stage resolved files
git rebase --continue # Continue rebasing
# 4. If stuck, abort and try again
git rebase --abort # Go back to original state
"I need to update my PR after review"
# Make changes
git add .
git commit -m "fix(review): address feedback on input validation"
# Update the PR (amend last commit or squash new commits)
git rebase -i origin/main # Squash fix commits into original
git push --force-with-lease # Update PR (safe force-push!)
Git Aliases I Can't Live Without
[alias]
co = checkout
br = branch
ci = commit
st = status -sb
lg = log --oneline --graph --all --decorate
unstage = reset HEAD --
amend = commit --amend --no-edit
recent = log --oneline -10
save = stash push -u -m
pop = stash pop
prune = !git branch --merged main | grep -v '\\*\\|main' | xargs git branch -d
wip = !git add -A && git commit -m \"WIP: $(date +%s)\"
done = !git checkout main && git pull && git prune
What's your git workflow? Any tips I'm missing?
Follow @armorbreak for more developer content.
Top comments (0)