When you're the only developer on a project, git can feel like overkill. Why branch when there's no one to conflict with? Why write commit messages when you know what you did?
But here's the thing: future you is a different person. Future you has forgotten why that "quick fix" touched seven files. Future you is desperately trying to bisect a regression and cursing past you's "wip" commits.
This is how I use git when I'm working solo — not because a team requires it, but because it makes me faster.
Feature Branches vs. Trunk-Based Development
The classic debate. Feature branches isolate work; trunk-based development keeps everything moving fast.
For solo work, I lean trunk-based with short-lived branches. Here's the decision tree:
Commit directly to main when:
- The change is small and self-contained (< 10 lines)
- You're confident it won't break anything
- You can verify it immediately
Use a branch when:
- The feature will take multiple commits
- You're experimenting and might abandon the work
- You want a clean squash point for history
My branches rarely live more than a day. If a branch is open for a week, something's wrong — either the scope is too big or I've lost focus.
The naming convention I use: feat/add-rss-feed, fix/dark-mode-toggle, refactor/auth-module. Short, descriptive, lowercase with hyphens.
Commit Message Conventions
I follow conventional commits, not because a tool requires it, but because it structures my thinking:
feat: add RSS feed generation
fix: correct dark mode toggle state
refactor: extract auth logic into module
docs: update README with setup instructions
chore: upgrade dependencies
The format: type: lowercase description
Keep the first line under 50 characters. If you need more detail, add a body after a blank line:
fix: prevent duplicate task creation
The heartbeat loop was firing twice when the system resumed
from sleep, causing duplicate tasks. Added a debounce check
using the last execution timestamp.
Closes #42
Why bother when you're solo? Because git log --oneline becomes scannable. Because git log --grep="fix:" finds all bug fixes. Because six months from now, "updated stuff" tells you nothing.
When to Squash vs. Merge
This depends on what story you want to tell.
Squash when:
- Your branch has messy work-in-progress commits
- The feature is one logical unit
- You don't care about the journey, just the destination
Merge (or rebase) when:
- Each commit represents a meaningful step
- You might need to revert part of the work later
- The commit history tells a useful story
For solo work, I squash probably 80% of the time. My branches often have commits like "wip", "fix typo", "actually fix it this time". Nobody needs to see that.
The command: git merge --squash feature-branch then commit with a clean message.
Handling Multiple Tasks in Parallel
Solo doesn't mean single-threaded. I often have 2-3 things in flight:
- A feature branch for the main work
- A quick fix that needs to ship now
- An experiment I'm not sure about
The workflow:
# Working on feature
git checkout feat/new-dashboard
# Urgent fix comes in
git stash
git checkout main
git checkout -b fix/critical-bug
# ... fix it ...
git checkout main
git merge --squash fix/critical-bug
git push
git branch -d fix/critical-bug
# Back to feature
git checkout feat/new-dashboard
git stash popThe key discipline: commit or stash before switching. Uncommitted changes following you between branches is how you lose work or create tangled commits.
For longer-running parallel work, I'll rebase my feature branch onto main periodically to avoid divergence:
git checkout feat/new-dashboard
git rebase mainSmall, frequent rebases are painless. Rebasing a week of divergent work is a nightmare.
Git Aliases for Productivity
These live in my ~/.gitconfig and save me hundreds of keystrokes daily:
[alias]
co = checkout
br = branch
ci = commit
st = status
# See what you're about to push
unpushed = log @{u}..HEAD --oneline
# Quick amend without editing message
amend = commit --amend --no-edit
# Pretty log
lg = log --oneline --graph --decorate -20
# Undo last commit, keep changes staged
undo = reset --soft HEAD~1
# Clean up merged branches
cleanup = "!git branch --merged | grep -v '\\*\\|main\\|master' | xargs -n 1 git branch -d"
# What did I do today?
today = log --since='midnight' --author='Owen' --onelineThe undo alias is a lifesaver. Committed too early? git undo, fix it, commit again.
The cleanup alias prevents branch accumulation. After a squash-merge, run git cleanup to delete the now-merged feature branch.
A few shell aliases I pair with these:
alias g='git'
alias gs='git status'
alias gp='git push'
alias gl='git pull'
alias gd='git diff'The Minimum Viable Workflow
If you take nothing else from this post, here's the essentials:
- Commit often — small commits are easier to understand and revert
- Write real messages — "fix bug" is not a message
- Branch for experiments — easy to abandon without polluting main
- Push daily — your laptop isn't a backup
- Use aliases — reduce friction, increase velocity
Git is a power tool. When you're solo, you get to use it your way, without committee approval or PR bureaucracy. That's a feature, not a limitation.
Build the workflow that makes you fast. Then stick to it until it's automatic.