Git branch graph in a terminal
Back to Blog
GitVersion ControlDeveloper ExperienceBest Practices

Git Workflows for Solo Developers and Small Teams

Git is powerful and easy to misuse. These are the habits and workflows that have kept my projects clean, recoverable, and easy to reason about.

Published on May 8, 20257 min read

Git Is Not Just a Backup System

I see a lot of developers use Git as a save button — commit when something works, push before going home. That's better than nothing, but it misses most of what Git offers: a readable history, safe experimentation, easy collaboration, and the ability to surgically undo specific changes.

Here's how I actually use it.

Commit Messages That Mean Something

A commit message answers: "What does this change do, and why?"

# ❌ Useless
fix stuff
wip
update

# ✅ Useful
fix: prevent duplicate order submission on rapid button clicks
feat: add PDF export to invoice detail page
refactor: extract address validation into shared utility
chore: upgrade Next.js to 14.2.0

I follow Conventional Commits (feat:, fix:, chore:, refactor:, docs:). It's lightweight, readable, and tools like semantic-release and conventional-changelog can generate changelogs from it automatically.

Commit Small and Often

One commit = one logical change. Not one file, not one hour of work — one logical change.

Benefits:

  • Bisecting is fast. git bisect can find exactly which commit introduced a bug when each commit is focused.
  • Reverting is safe. Reverting a commit that does one thing doesn't blow away three unrelated changes.
  • Code review is easier. Reviewers can understand each change in isolation.

Branching Strategy for Solo and Small Teams

For solo work or a team of 2–4, I use a simplified GitHub Flow:

main         — always deployable, protected
feature/*    — one branch per feature or fix

The rules:

1. Never commit directly to main.

2. Create a branch from main, do your work, open a PR (even solo — it forces you to review your own diff).

3. Merge via squash for small features, merge commit for large ones.

4. Delete the branch after merging.

No develop branch, no release branches unless you need them. Complexity should match team size.

Aliases That Save Time

These are in my ~/.gitconfig:

[alias]
  st = status -sb
  co = checkout
  lg = log --oneline --graph --decorate --all
  undo = reset --soft HEAD~1
  oops = commit --amend --no-edit

git lg gives you a readable, visual branch graph. git undo un-commits the last commit but keeps your changes staged. git oops adds staged changes to your last commit without changing the message.

The Stash Is Your Friend

Before switching branches mid-feature:

git stash push -m "wip: half-done cart total calculation"
git checkout main
# do the urgent thing
git checkout feature/cart
git stash pop

I always add a message to stashes. git stash list with unnamed stashes is a mystery list.

Recovering from Mistakes

Almost nothing in Git is truly lost within 90 days. git reflog shows every operation Git performed — including commits you "deleted" with a reset:

git reflog
# find the commit hash you want
git checkout -b recovery-branch <hash>

Knowing this exists makes you less afraid to experiment.

Pre-commit Hooks with Husky

I run lint and type-check before every commit so broken code never enters the history:

pnpm add -D husky lint-staged
npx husky init

pnpm lint-staged

"lint-staged": {
  "*.{ts,tsx}": ["eslint --fix", "prettier --write"]
}

Slow CI feedback loops become fast local feedback.