The Git Workflow That Saved My Sanity as a Solo Developer
After years of trial and error, this is the workflow I use for every project.
Branch Strategy: Trunk-Based Development
main (always deployable)
│
├── feature/user-auth (short-lived branch)
│ └── merge → main
│
├── fix/login-bug (hotfix)
│ └── merge → main
│
└── experiment/ai-suggestions (might be abandoned)
└── only merge if it works out
# Start new work from latest main
git checkout main
git pull origin main
git checkout -b feature/my-feature
# Work, commit, work, commit...
# Push branch for review / backup
git push -u origin feature/my-feature
# When done:
# Option A: Merge to main
git checkout main
git pull origin main
git merge --no-ff feature/my-feature # Preserve history
git push origin main
# Option B: Squash into one clean commit (for messy WIP branches)
git checkout main
git pull origin main
git merge --squash feature/my-feature
git commit -m "Feature: Add user authentication"
git push origin main
My Commit Convention
# Format: type(scope): subject
# body (optional)
# footer (optional)
feat(auth): add OAuth2 Google login
- Implement Google OAuth2 flow
- Create/update user on first login
- Store refresh token for API access
Closes #123
Types I Use
| Type | When |
|---|---|
feat |
New feature |
fix |
Bug fix |
refactor |
Code restructuring (no behavior change) |
docs |
Documentation only |
style |
Formatting, semicolons, etc. (no logic change) |
perf |
Performance improvement |
test |
Adding/updating tests |
chore |
Build, config, dependencies |
revert |
Revert a previous commit |
Pre-Commit Hook (Catches Mistakes Before They Happen)
// .husky/pre-commit (or package.json "lint-staged" config)
{
"lint-staged": {
"*.{js,ts}": [
"eslint --fix",
"prettier --write",
"git add"
],
"*.{json,md,yml,yaml}": [
"prettier --write",
"git add"
],
"*.{css,scss,html}": [
"stylelint --fix",
"git add"
]
}
}
# Install:
npm install -D husky lint-staged prettier eslint
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"
# Now every commit is automatically formatted and linted!
Interactive Staging (Commit Only What You Want)
# See what changed
git status
# Stage specific files
git add src/utils.js src/api.js
# Or stage parts of a file (hunk by hunk)
git add -p
# The interactive prompt:
# y = stage this hunk
# n = don't stage this hunk
# s = split into smaller hunks
# e = edit this hunk manually
# ? = help
# Review before committing
git diff --cached # Shows exactly what will be committed
git commit # Only staged changes are included
The Undo Toolkit
# Modified a file but want to start over?
git checkout -- filename # Discard working directory changes
# Committed too early (want to modify the last commit)?
git commit --amend # Edit message or add more changes
# Committed to wrong branch?
git reset HEAD~1 # Undo commit, keep changes unstaged
git checkout correct-branch
git add .
git commit
# Pushed something you shouldn't have?
git revert SHA # Creates a NEW commit that undoes the change
# Safer than force-push!
# Need to update a PR after review?
git commit -m "fix: address reviewer feedback"
git push # Just push new commits (don't rebase after pushing!)
Keeping History Clean
# Before merging a feature branch, clean up the history:
# 1. See recent commits
git log --oneline -10
# 2. Interactive rebase (squash WIP commits)
git rebase -i HEAD~5
# In the editor that opens:
pick abc1234 WIP: started work
squash def5678 WIP: more stuff
squash ghi9010 fix typo
pick jkl1123 add tests
pick mno3456 final cleanup
# Change squash to combine WIP commits into one meaningful commit:
pick abc1234 feat: implement user auth
pick jkl1123 test: add unit tests for auth
pick mno3456 style: format code
# Save and exit → clean history!
Working With Others (or Across Machines)
# Before starting work ALWAYS:
git pull origin main # Get latest changes
# If there are conflicts after pulling:
# 1. Don't panic
# 2. Open each conflicted file
# 3. Look for <<<<<<< HEAD (your changes) and ======= (their changes)
# 4. Decide which to keep (or combine both)
# 5. Remove the conflict markers
# 6. git add <resolved-file>
# 7. git continue (if using rebase) or git commit (if using merge)
# Quick conflict resolution tip:
# Accept yours: git checkout --ours filename
# Accept theirs: git checkout --theirs filename
# Then: git add filename && git continue
My Daily Git Commands
# Morning: check status of everything
git status # What's changed?
git log --oneline -5 # Recent commits
git branch -vv # All branches and their status
# During work:
git diff # Unstaged changes
git diff --cached # Staged changes (what will be committed)
git stash # Save work temporarily
git stash pop # Get it back
# Before pushing:
git log origin/main..HEAD --oneline # What am I about to push?
git diff --stat origin/main..HEAD # How much changed?
# After pushing:
git push origin main
Aliases That Save Me Time
[alias]
co = checkout
br = branch
ci = commit
st = status -sb
lg = log --oneline --graph --all --decorate
unstage = reset HEAD --
amend = commit --amend --no-edit
prune-all = !git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -d
save = !git add -A && git stash
pop = stash pop
undo = reset HEAD~1
diffstat = diff --stat
recent = log --oneline -10
What's your favorite Git workflow tip? Anything here you'd do differently?
Follow @armorbreak for more developer content.
Top comments (0)