A simple Git branching workflow for small teams
Mosaddik Billah 6 min read

On this page
Search for “Git branching strategy” and you’ll find diagrams with five kinds of branches, arrows going everywhere, and names like release/2.3.1-hotfix. Those workflows exist for good reasons, in big teams shipping versioned software. For a small team building a website, an app or an API, they’re mostly overhead.
This is the workflow I use and recommend for teams of one to about ten people. It has one rule at its centre, and everything else follows from it.
The one rule: main is always deployable#
main is the branch that goes live. Whatever is on main should be safe to deploy at any moment. Nobody commits half-finished work straight to it.
That’s it. Everything below is just a way to keep that promise:
- Start every change on a short-lived branch off
main. - Open a pull request when it’s ready.
- Someone (or at least the automated checks) reviews it.
- Merge into
main, and deploy. - Delete the branch.
If that sounds like GitHub Flow, it is, more or less. It works because it’s simple enough that people actually follow it.
1. Start a branch#
Always start from an up-to-date main:
git switch main
git pull
git switch -c add-contact-formgit switch -c creates the branch and moves you onto it. (Older tutorials use git checkout -b, which does the same thing.)
Name branches after what they do, in a few words: add-contact-form, fix-checkout-tax, update-pricing-page. Some teams add a prefix like fix/ or feature/, or a ticket number. Pick one style and stick with it; the exact style matters much less than consistency.
2. Commit as you go#
Make small commits with messages that say what changed and, when it isn’t obvious, why:
git add src/pages/contact.astro
git commit -m "Add contact form with name, email and message fields"A good test: could a teammate read just the commit messages in six months and understand roughly what happened? “fix”, “wip” and “changes” fail that test.
3. Push and open a pull request#
git push -u origin add-contact-formThe -u links your local branch to the one on GitHub (or GitLab, or Bitbucket), so after this a plain git push works.
Then open a pull request. In the description, write three things: what changed, why, and how to check it. A screenshot helps for anything visual. It takes two minutes and saves the reviewer twenty.
Open the pull request early if you want feedback, and mark it as a draft. It’s much cheaper to hear “we already have a component for that” on day one than on day five.
4. Review and automated checks#
Two kinds of checks, and you want both:
- Automated: tests, a linter, a build. Set them up to run on every pull request (GitHub Actions or similar) so nobody has to remember. If the build fails, it doesn’t merge. Which tests are worth having is a whole topic; I covered it in the testing pyramid.
- Human: someone reads the change. On a team of two, that’s the other person. If you work alone, read your own diff on the pull request page before merging. It’s surprising what you catch when you see the code somewhere other than your editor.
In the repository settings, turn on branch protection for main: require a pull request, and require the checks to pass. Then the rule isn’t just a rule, it’s enforced.
5. Keep your branch up to date#
If main moves on while you’re working, bring those changes into your branch before merging:
git switch main
git pull
git switch add-contact-form
git merge mainIf there are conflicts, Git stops and marks them in the files. Fix them, then git add the files and git commit. Some teams prefer git rebase main instead of merging, which keeps history in a straight line. Either is fine; just don’t rebase a branch someone else is also working on.
The best way to avoid painful conflicts is the next point.
6. Merge small, merge often#
Long-lived branches are where this all goes wrong. A branch that lives for three weeks drifts so far from main that merging it is a project of its own, and reviewing two thousand changed lines is something nobody does properly.
Aim for branches that last a day or two. If a feature is bigger than that, split it:
- Merge the database change first, then the backend, then the page.
- Hide unfinished features behind a simple flag (
if (FEATURES.newCheckout)) so the code can be onmainwithout being visible to users.
Small pull requests get reviewed faster, break less, and are easy to undo.
7. Merge, deploy, clean up#
Once it’s approved and green, merge it on the pull request page. Most teams use squash and merge, which turns all the branch’s commits into one tidy commit on main. Then:
git switch main
git pull
git branch -d add-contact-formIf you deploy automatically from main (Vercel, Netlify and most hosts can do this), merging is deploying. That’s exactly why the one rule matters.
When something breaks in production#
It will, eventually. Because main is always deployable and every change came in as one small merge, fixing it is calm:
- To undo a change: find the merge commit and run
git revert <commit>, then push it through a pull request like any other change. GitHub also has a Revert button on merged pull requests. Revert creates a new commit that undoes the old one; it doesn’t rewrite history, so it’s safe on a shared branch. - To fix forward: make a branch called something like
fix-checkout-total, fix it, and merge it the usual way. No special hotfix process needed.
What you shouldn’t do is git push --force to main. Protect the branch so nobody can, including you on a bad day.
When you need more than this#
This workflow starts to strain if you ship versioned releases that customers install (a mobile app, a desktop app, a library) and need to support several versions at once. Then release branches earn their keep. It also changes if you need a separate staging environment that QA signs off on before every release. For websites, web apps and APIs with a small team, though, this is usually all you need, and adding more process mostly adds technical debt of a different kind.
If you’d like a hand setting up a codebase, deployment and review process that your team will actually follow, I do API integration and custom development work.
Comments