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:

  1. A feature branch for the main work
  2. A quick fix that needs to ship now
  3. 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 pop

The 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 main

Small, 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' --oneline

The 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:

  1. Commit often — small commits are easier to understand and revert
  2. Write real messages — "fix bug" is not a message
  3. Branch for experiments — easy to abandon without polluting main
  4. Push daily — your laptop isn't a backup
  5. 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.

React to this post: