Git gives you enormous freedom, and that is exactly the problem. There are a dozen ways for a team to use it, and without agreement, you end up with tangled histories, surprise merge conflicts, and the dreaded message "who broke main?" The good news is that small teams do not need an elaborate branching model. A simple, consistent workflow is enough.
This article describes a lightweight approach that works well for teams of two to ten people building a web app. It borrows the best parts of what is often called trunk-based development and GitHub Flow, without the ceremony.
The core idea: main is always deployable
Treat your main branch as the source of truth. It should always build, pass its tests, and be safe to deploy. Nobody commits half-finished work straight to it. Instead, all work happens on short-lived branches that are merged back through a pull request.
This single rule removes most of the anxiety around Git. If main is always healthy, anyone can pull it at any time and start new work from a trustworthy base.
Step 1: Branch for every piece of work
Create a branch from the latest main for each task, whether it is a feature, a bug fix, or a small refactor. Name branches so teammates can tell what they are about at a glance.
git switch main
git pull
git switch -c fix/login-redirect-loop
Prefixes like feature/, fix/, and chore/ are a lightweight convention that makes the branch list readable. Keep branches short-lived. A branch that lives for two weeks accumulates conflicts and becomes frightening to merge. Aim to merge within a day or two, and split large tasks into smaller ones that can each be merged independently.
Step 2: Commit small and write useful messages
A commit should be one logical change that you can describe in a sentence. Committing "everything I did today" makes history useless for debugging and reverting. Commit often, and stage deliberately so unrelated edits do not sneak in.
git add -p # review and stage changes hunk by hunk
git commit -m "Redirect to dashboard after login only once"
A good commit message has a short summary line in the imperative mood, such as "Add password reset email", and, when needed, a blank line followed by a paragraph explaining why the change was made. The code already shows what changed. The message should explain the reason, because that is the part nobody can reconstruct later.
Step 3: Keep your branch up to date
While you work, main keeps moving as teammates merge their changes. Pull those changes into your branch regularly so that conflicts appear in small pieces rather than all at once.
git fetch origin
git rebase origin/main # or: git merge origin/main
Teams argue about merge versus rebase. Either is fine if you pick one and stay consistent. Rebasing gives a cleaner linear history; merging is simpler and never rewrites commits. A safe rule: only rebase branches that you alone are working on, and never rewrite history that others have already pulled.
Step 4: Open a small pull request
A pull request is a conversation about a change. The smaller it is, the better the conversation. Reviewers can genuinely understand a 150-line change; a 1,500-line change gets a rubber-stamp "looks good to me". Write a short description that covers what changed, why, and how to test it. Add screenshots for visual changes.
Link the pull request to the task or issue it addresses, and mark it as a draft if you want early feedback before it is ready. Keep unrelated cleanups out of the diff, since they make review harder.
Step 5: Review quickly and kindly
Review latency is the silent killer of team velocity. If pull requests wait two days for a response, developers start stacking branches on top of each other, and conflicts multiply. Agree on a team norm, for example that reviews are looked at within one working day. Reviewers should focus on correctness, clarity, and risk, and leave style arguments to an automated formatter.
Step 6: Merge and clean up
When the review is approved and checks pass, merge the pull request. Many teams prefer "squash and merge", which collapses the branch into a single commit on main, keeping history tidy. Others prefer to keep individual commits. Decide once and write it down. After merging, delete the branch so the repository does not fill up with stale ones.
Protect main
Turn on branch protection in your hosting service. Require at least one approval and passing checks before merging, and block direct pushes to main. This is not about distrust. It is a safety net that makes mistakes harder to make, especially on a tired Friday afternoon.
Handle hotfixes without panic
Production is broken and you need a fix now. The workflow does not change: branch from main, make the smallest possible fix, open a pull request, get a fast review, and merge. Because main is always deployable, you can ship the fix immediately. Resist the urge to commit directly to main, since that is how a quick fix becomes a second outage.
Write it down
The best Git workflow is the one everyone actually follows. Put the rules in a short CONTRIBUTING.md: how to name branches, how to write commit messages, how big a pull request should be, and how reviews work. New teammates will thank you, and you will stop having the same argument twice.
The takeaway
For a small team, the winning recipe is simple: keep main healthy, branch for every task, commit in small meaningful steps, open small pull requests, review fast, and merge often. You do not need a complicated branching model. You need consistency, and a team that treats the history as something worth keeping clean.