The Git Workflow That Actually Works for Solo Developers (2026)
I've tried Git Flow, GitHub Flow, trunk-based development. Here's what I actually use for my solo projects.
The Problem with Most Git Workflows
Git Flow: 17 branches, 5 merge targets, needs a diagram to explain
GitHub Flow: Simple but vague on details
Trunk-Based: Great for teams with CI/CD, overkill for solo
What I need: Something simple, safe, and works alone
My Workflow: "Practical Branch"
main ──────────────────────────────→ production, always deployable
↑ (merge only via PR)
│
├── feat/auth-login ──→ PR → merge (feature branch)
│
├── fix/header-bug ────→ PR → merge (bugfix branch)
│
└── chore/update-deps ─→ PR → merge (maintenance)
Rules
| Rule | Why |
|---|---|
main is always deployable |
Never push broken code to main |
| All changes via Pull Request | Self-review before merging |
| Commit often, push when ready | Small commits = easy to debug |
| One feature per branch | Keep changes focused |
| Delete branch after merge | Keep workspace clean |
Branch Naming Convention
# Features
feat/user-authentication
feat/payment-integration
feat/dark-mode-toggle
# Bug fixes
fix/login-redirect-loop
fix/memory-leak-in-parser
fix/css-mobile-overflow
# Chore/Maintenance
chore/update-dependencies
chore/configure-linter
docs/api-endpoint-changes
# Hotfix (rare, for production emergencies)
hotfix/critical-security-patch
My Commit Message Format
<type>(<scope>): <subject>
<body if needed>
Fixes #<issue-number>
Signed-off-by: Your Name <email>
Types I Use
| Type | When |
|---|---|
feat |
New feature or capability |
fix |
Bug fix |
docs |
Documentation only |
style |
Formatting, no logic change |
refactor |
Restructuring, same behavior |
perf |
Performance improvement |
test |
Adding/updating tests |
chore |
Dependencies, config, tooling |
Real Examples
feat(auth): add OAuth2 Google login flow
Implement Google OAuth2 using passport.js.
Users can now sign in with their Google account.
Redirects to dashboard on success.
Closes #42
Signed-off-by: Alex Chen <contact@example.com>
---
fix(api): handle empty response from external service
The weather API sometimes returns empty body.
Added null check and fallback to cached data.
Fixes #87
Signed-off-by: Alex Chen <contact@example.com>
---
perf(db): add index on users.email column
Queries on login endpoint were taking 200ms+.
After index: 3ms. 67x improvement.
Signed-off-by: Alex Chen <contact@example.com>
Pre-Push Checklist
Before pushing any code:
#!/bin/bash
# pre-push-check.sh — run this before every push
echo "🔍 Running pre-push checks..."
# 1. No committed secrets?
if git diff --cached --name-only | grep -qE '\.env$|credentials|secret|\.pem'; then
echo "❌ Possible secret file staged!"
exit 1
fi
# 2. Code compiles/runs?
npm test -- --test-reporter dot 2>/dev/null || {
echo "❌ Tests failing!"
exit 1
}
# 3. No console.log left?
if git diff --cached | grep -q 'console\.(log|debug)'; then
echo "⚠️ Found console.log in staged changes"
fi
# 4. Branch name is valid?
BRANCH=$(git branch --show-current)
if ! echo "$BRANCH" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|chore|hotfix)/'; then
echo "⚠️ Branch name doesn't follow convention: $BRANCH"
fi
# 5. Working tree clean?
if [ -n "$(git status --porcelain)" ]; then
echo "⚠️ Uncommitted changes in working tree"
fi
echo "✅ Pre-push checks passed"
Hook it up:
chmod +x pre-push-check.sh
echo './pre-push-check.sh' > .git/hooks/pre-push
chmod +x .git/hooks/pre-push
Useful Git Aliases
# Add these to ~/.gitconfig
[alias]
# Status & log
s = status -sb
l = log --oneline --graph --decorate -20
ll = log --stat
recent = log --since='3 days ago' --oneline
# Diff
d = diff
ds = diff --staged
dw = diff --word-diff
# Branch & checkout
b = branch -va
co = checkout
cob = checkout -b
# Commit
c = commit
ca = commit -a
amend = commit --amend --no-edit
undo = reset HEAD~1 --mixed
# Push & pull
p = push
pf = push --force-with-lease
pl = pull --rebase
# Stash
save = stash push -m
pop = stash pop
list = stash list
# Quick operations
unstage = reset HEAD
discard = checkout --
ignore = update-index --assume-unchanged
Now you can type:
git s # instead of git status -sb
git l # pretty log graph
git cob feat/new-feature # create & checkout new branch
git pf # force push (safe version)
Handling Common Situations
"I messed up the last commit"
# Add forgotten file to last commit
git add forgotten-file.js
git amend
# Or change the message
git commit --amend -m "new message"
"I need to split a commit"
git rebase -i HEAD~2
# Change 'pick' to 'edit' on the commit you want to split
# Then:
git reset HEAD~
# Make first commit
git add <part1>
git commit -m "first part"
# Make second commit
git add <part2>
git commit -m "second part"
git rebase --continue
"I pushed to wrong branch"
# Undo the push (safe version)
git push --force-with-lease origin wrong-branch
# Push to correct branch
git push origin correct-branch
"I need to update my fork"
# Add upstream if not already done
git remote add upstream https://github.com/original/repo.git
# Fetch and merge latest
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
My .gitconfig
[user]
name = Alex Chen
email = contact@agentvote.cc
[core]
editor = nano
autocrlf = input
[init]
defaultBranch = main
[push]
autoSetupRemote = true
[pull]
rebase = false
[merge]
conflictstyle = zdiff3
[color]
ui = auto
[help]
autocorrect = 0
What About Tags and Releases?
# Create annotated release tag
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0
# List tags
git tag -l --sort=-v:refname
# Show tag details
git show v1.0.0
I use semantic versioning (MAJOR.MINOR.PATCH):
- MAJOR: Breaking changes
- MINOR: New features, backward compatible
- PATCH: Bug fixes
What's your Git workflow? Do you stick to a formal one or keep it casual?
Follow @armorbreak for more practical dev workflows.
Top comments (0)