Most git tutorials are written for teams. They describe pull request workflows that require at least two people, branch protection rules that need an administrator to configure, and review processes that assume someone is waiting on the other end. If you build software alone, you have likely read these guides, nodded along, then quietly ignored most of them — because none of it maps to the reality of shipping code by yourself at 11 PM on a Tuesday.
The problem is not that git workflows for solo developers are poorly documented. The problem is that they are barely documented at all. Most resources either treat version control as a team coordination problem or reduce it to “commit often and push to main.” Neither extreme serves a solo developer running one or more real products.
What follows is not the textbook approach. It is the approach that comes from years of shipping solo — the actual decisions, the traps, and the practices that hold up under the specific pressures of building alone.
Why Gitflow Is the Wrong Mental Model for One-Person Projects
Gitflow was designed by Vincent Driessen in 2010 to solve a specific coordination problem: multiple developers working on multiple features simultaneously, with a hard release boundary separating development from production. It is a good solution to that problem. It is a terrible solution if that problem is not yours.
For a solo developer, Gitflow introduces branches you will never actually use as intended. A develop branch becomes a place where half-finished work accumulates because there is no other reviewer gating what goes in. Release branches get created and then merged in the same session because the “release process” is just you running a deploy script. Hotfix branches feel procedurally correct and logistically absurd — you already know where the bug is, you already know this is urgent, and the overhead of branching from main, fixing, merging to main, then merging back to develop adds ten minutes to a five-minute fix.
GitHub Flow is simpler and better suited to continuous deployment, but it still assumes the fundamental unit of work is a feature branch reviewed by someone else. When you are the reviewer, the branch is just a delay.
The mental model worth internalizing for solo work is not “how do I coordinate with others” but “how do I protect myself from myself.” Those are different problems requiring different solutions.
Trunk-Based Development With Feature Flags: The Pragmatic Default
The approach that holds up best for solo projects is committing directly to main with feature flags controlling what is actually live. Not because it is fashionable, but because it matches the actual constraint: you do not have integration lag from parallel contributors, and your main risk is deploying something broken, not merging conflicting feature work.
Feature flags do not require a third-party service. For most solo projects, a simple environment variable or a row in a config table is sufficient. The pattern looks like this:
- New features ship to
mainbehind a flag set tofalsein production - The code is always in a deployable state — incomplete features exist in the codebase but are unreachable by users
- When the feature is ready, flipping the flag is a one-line config change, not a branch merge
- Rollback is flipping the flag back, not reverting commits
This approach eliminates the category of problem where you have been working in a branch for two weeks and the merge back to main is now a negotiation with divergent history. There is no divergent history. You have been committing to the same branch the whole time, and the divergence lives in your flag configuration, not your git graph.
The objection is usually “what if the code for the unfinished feature is messy?” The answer is: it was going to be messy on a branch too. The flag approach just forces you to keep the codebase compilable and deployable at every commit, which is actually a higher discipline bar than branching — and a useful one.
When Branches Actually Earn Their Existence
Ruling out Gitflow does not mean ruling out branches entirely. There are three scenarios where a branch is genuinely the right tool for a solo developer, and recognizing them clearly prevents the opposite failure: never branching at all and having no recovery point when an experiment turns destructive.
Genuine experiments with uncertain outcomes. If you are spiking a fundamentally different approach — rewriting a data model, migrating an ORM, trying a new rendering strategy — and you genuinely do not know if it will work, a branch is appropriate. The key qualifier is “genuinely do not know.” If you know it will work and you are just implementing it, that is a feature, and it belongs behind a flag on main. Experiments are things you might need to abandon entirely, which means you need a clean way to do so without reverting a dozen commits on main.
Client work or multiple deploy targets. If you maintain a SaaS that has a white-labeled version for a specific enterprise client, or if you have a product deployed to multiple environments with different feature sets, long-lived branches per deployment target are legitimate. This is not Gitflow — these branches represent genuinely divergent deployment states, not coordination checkpoints. Managing them requires rigor about what gets cherry-picked versus what stays isolated, but the branch itself is justified.
Breaking changes you are not ready to ship. Occasionally you start refactoring and realize the change is deeper than expected, and you will not finish it today. A branch lets you park that work without putting main in a broken intermediate state. The discipline here is keeping the branch short-lived — days, not weeks. If a refactor branch lives longer than a week, it has become a context-switching liability, not a safety mechanism.
The Weekend Branch Anti-Pattern
There is a specific failure mode that appears in solo developer repositories with a regularity that suggests it is structural, not accidental. Call it the weekend branch.
It starts on a Friday or Saturday afternoon. You have uninterrupted time, a clear head, and a big idea. You create a branch — maybe called feature/new-dashboard or refactor/auth-overhaul — and spend four or six hours making real progress. Then Sunday ends, the workweek starts, and the branch does not get touched for nine days. When you return to it, you have forgotten the intermediate state, main has moved forward, and the merge is now genuinely complicated.
The weekend branch anti-pattern is not really about weekends. It is about long-lived branches on solo projects that were created for the wrong reason. The developer was not experimenting in the sense of “this might not work.” They were just building a feature during a stretch of flow and defaulted to branching out of habit or team convention.
The fix is twofold. First, use flags instead of branches for features you intend to ship — they do not diverge from main and do not require catching up when you return. Second, if you do create a branch, put a personal deadline on it. A branch with no merge target date on a solo project is a branch that will cause problems. Treat it like a time-boxed investigation, not an open-ended work stream.
Commit Message Discipline When No One Is Reviewing
The most common argument for writing good commit messages is that your colleagues need to understand your changes. Remove the colleagues, and many solo developers conclude the commit message does not matter. This is correct in the short term and expensive in the medium term.
Six months into a project, you will bisect a bug and land on a commit called fix stuff. You will have no idea what “stuff” was fixed, why the fix took that approach, or whether the surrounding code was intentional or temporary. You will spend thirty minutes in archaeological mode reconstructing a decision you made in ten minutes and then described in two words.
The standard that works for solo projects is lighter than conventional commit guidelines but heavier than “fix stuff.” The format worth adopting:
- First line: imperative verb, subject, 50 characters or fewer — what the commit does
- Blank line
- Body: one to three sentences explaining why, not what — the what is in the diff, the why is not
The “why” is what matters. “Remove debounce from search input” tells you nothing useful six months later. “Remove debounce from search input — users on mobile reported lag; real-time feels better and server can handle the load at current scale” tells you the tradeoff that was made and the conditions under which it might need revisiting.
Enforcing this discipline when you are the only committer requires making it easier to write a good message than to write a bad one. The practical tool is a commit message template in your global .gitconfig:
Set
commit.templateto a file containing your preferred structure — verb, subject, blank line, why-body placeholder. Git will open it in your editor on every commit, and the visual scaffolding alone reduces the rate of lazy messages significantly.
Tagging and Release Strategy for Projects With No Release Manager
Solo projects tend to ship continuously with no formal release events, which leads to repositories where every commit on main is nominally “production” but no version is ever named. This creates a specific problem: when something breaks, you cannot quickly identify what changed between “working last week” and “broken right now” without reading diffs manually.
A lightweight tagging strategy eliminates this without requiring a release process. The convention that holds up well for solo continuous deployment:
- Tag every deployment with a semantic version —
v1.4.2— before pushing to production - Use annotated tags, not lightweight tags:
git tag -a v1.4.2 -m "Add payment retry logic; fix session expiry bug" - Automate the tag push in your deploy script so it happens consistently rather than depending on memory
Annotated tags are searchable, carry a timestamp, and support a message — which means your deployment history becomes a chronological log of intentional release points with human-readable descriptions. Combined with reasonable commit messages, this gives you enough forensic capability to debug regressions without needing a full changelog system.
Semantic versioning is worth using even when there is no external API contract. The patch/minor/major distinction forces you to consciously categorize each release, which surfaces the occasional “wait, this is actually a breaking change” realization before it reaches users rather than after.
Rebase vs. Merge When You Are the Only Committer
The rebase versus merge debate is usually framed as a team philosophy question about history cleanliness. For solo developers, the answer is more mechanical: rebase almost always, merge for a specific reason.
When you are the only committer, a merge commit is typically noise. It represents the act of combining branches that existed because of the branching model you chose, not because of actual parallel work. Merge commits in a solo repository look like coordination overhead without the coordination. They make git log --oneline harder to read and bisection slightly more complex.
Rebasing your feature branch onto main before merging (or just fast-forwarding) keeps the history linear. Linear history is easier to navigate, easier to bisect, and communicates a cleaner narrative of how the codebase evolved — which matters when you are reading your own history months later.
The one situation where merge is clearly correct for solo work: merging a long-lived client branch back into a shared base. Here the merge commit is meaningful — it represents a deliberate integration event between genuinely divergent lines of development, and you want that event to be visible in the history. In this case, --no-ff is appropriate and the commit message should describe what the integration accomplished.
The golden rule is that if you would be embarrassed to show the git log to a future version of yourself, fix it before it gets to main. Interactive rebase — git rebase -i — is the tool for squashing fixup commits, reordering commits for logical clarity, and editing messages on commits that got lazy names. Use it freely on local branches before they touch main.
Practical .gitconfig Aliases Worth Installing Today
Configuration is where solo git practice either degrades or compounds positively over time. The developers who have been doing this for years tend to have heavily customized .gitconfig files that eliminate dozens of micro-frictions per day. Here are the aliases that pull weight most consistently:
lg: A formatted log that shows branch graph, author, date, and message on one line. The standardgit logoutput is verbose and hard to scan. A goodlgalias turns the history into something you will actually look at:log --oneline --graph --decorate --allundo:reset HEAD~1 --mixed— un-commits the last commit and leaves the changes staged. Useful when you committed too early or want to restructure a commit before it becomes permanent.wip:!git add -A && git commit -m "WIP"— a fast save point when you need to context-switch urgently and do not have time to write a real commit. The convention is to always amend or squash WIP commits before they reach main.aliases:config --get-regexp alias— lists all configured aliases, because every solo developer eventually forgets what they have set up.ignored:ls-files --others --ignored --exclude-standard— shows everything gitignore is actually hiding, useful when you suspect a file is being silently excluded.
Beyond aliases, two global settings that solo developers frequently overlook: set push.default to current so git push without arguments pushes the current branch to a remote of the same name. And set pull.rebase to true so that pulling from a remote automatically rebases rather than creating a merge commit — which, on solo projects, is almost always what you actually want.
The Actual Goal: A Repository You Can Reason About in Six Months
Every git decision on a solo project comes back to a single question: will you be able to reconstruct your own reasoning when you return to this code later? Not will your teammates understand it — you have no teammates. Will you understand it, when the context has faded and the only record of your thinking is what you committed?
The practices outlined here are not about following conventions for their own sake. Trunk-based development with flags keeps the codebase in a deployable state at all times, which is a constraint that prevents a particular class of self-inflicted problems. Good commit messages are notes to future-you. Annotated tags create a deployment timeline you can navigate. Rebasing keeps history linear and readable. None of these are hard. They compound slowly into a repository that is a genuine asset rather than a pile of undated diffs.
The developers who get the most value out of version control on solo projects are not the ones who rigorously implement a named branching strategy. They are the ones who have internalized why each practice exists and apply it proportionately to their actual situation. A solo developer does not need Gitflow’s coordination machinery. They need a different set of habits — ones designed for the specific challenge of being your own past, present, and future collaborator.
Start with the .gitconfig cleanup. Fix the commit messages on your next project. Add a deploy tag to the next production push. The compound effect of small, consistent practices outperforms any branching model you can borrow from a team that does not share your constraints.