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.0I 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 bisectcan 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 fixThe 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-editgit 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 popI 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 initpnpm lint-staged
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"]
}Slow CI feedback loops become fast local feedback.
