Git branching strategies: trunk-based, GitHub Flow and Git Flow
Which branching model should your team use? Compare three popular strategies and see how merge, rebase and squash change your history.

A branching strategy is a team agreement about how work moves from an idea to the main code: where you create branches, how long they live, and how they come back. There is no single best answer, but there are good defaults. This article compares three common models and shows what merge, rebase and squash merge do, using real git log --graph output from a small test repository. It assumes you know basic Git (see our Git and GitHub for beginners article).
Branch basics in one minute
A branch is a movable label on a commit. Creating one is nearly free. Use git switch -c feature/login to create and move to a branch, commit as usual, then bring it back into main by merging.
Strategy 1: GitHub Flow (simple and popular)
mainis always deployable.- For each change, create a short branch from
main(feature/login,fix/typo). - Open a pull request and get review and passing tests.
- Merge into
main, then deploy, and delete the branch.
Good for: web apps and services that deploy often, small and medium teams. Weak spot: it does not describe how to maintain several released versions at once.
Strategy 2: Trunk-based development
Everyone commits to main (the "trunk"), either directly or through very short branches that live hours to a day or two. Unfinished work is hidden behind feature flags (a setting that switches a feature on or off) so it can be merged safely without being visible to users. It relies on strong automated tests and fast CI.
Good for: teams that deploy many times a day and have good tests. Several large engineering teams use it. Weak spot: needs discipline, tests and feature flags, which a beginner team may not have yet.
Strategy 3: Git Flow (heavier)
Uses several long-lived branches: main for releases, develop for integration, plus feature/*, release/* and hotfix/* branches. Work merges into develop, a release branch is cut and stabilised, then merged to main and tagged.
Good for: products with scheduled releases and several supported versions, such as desktop software, mobile apps or libraries. Weak spot: more branches, more merging and slower feedback. Its original author later noted that it is not ideal for continuously delivered web apps.
Comparison
| GitHub Flow | Trunk-based | Git Flow | |
|---|---|---|---|
| Long-lived branches | only main |
only main |
main + develop |
| Branch lifetime | days | hours to 1-2 days | days to weeks |
| Needs feature flags | sometimes | usually | rarely |
| Release style | deploy from main |
deploy from main |
planned releases |
| Complexity | low | low, but needs strong tests | high |
A sensible default for most small teams: GitHub Flow, with branches kept short. Move towards trunk-based as your tests and deploys improve. Use Git Flow only if you really ship versioned releases.
Merge, rebase and squash: what happens to history
All three bring a branch's work into main. I ran each in a test repository where main had moved on while the feature branch was being built.
Merge commit (git merge --no-ff)
* f8aadb2 Merge feature/login
|\
| * e8beb18 Add login form
* | c9c29a9 Fix typo on home page
|/
* b508c31 Start the app
History is preserved exactly as it happened, with a merge commit that joins the two lines. It is safe and never rewrites commits. The graph can get busy on large projects.
Rebase, then fast-forward
* d4ab35e Add login form
* 903c05e Fix typo on home page
* b508c31 Start the app
git rebase main replays your branch's commits on top of the latest main, giving new commit ids, and then git merge --ff-only moves main forward in a straight line. The history is clean and linear. The cost: rebasing rewrites commits.
Golden rule: never rebase commits that other people already use (a shared branch or
main). Rebase only your own local or personal branches. If you must push a rebased branch you already pushed, usegit push --force-with-lease, which refuses to overwrite work you have not seen.
Squash merge
9cfe613 Add profile page
d4ab35e Add login form
903c05e Fix typo on home page
b508c31 Start the app
git merge --squash turned two "wip" commits into one clean commit, "Add profile page", on main. Great for tidy history and easy reverts of a whole feature. The cost: individual commits of the branch are not kept in main, and the branch must be deleted afterwards (Git does not know it was merged).
On GitHub, you can pick one of these three on the pull request's merge button. Teams choose one and stick to it.
Good habits whatever strategy you choose
- Keep branches short and small. Long-lived branches cause painful conflicts. Merge
maininto your branch (or rebase) often. - Name branches clearly:
feature/login,fix/null-error,docs/readme, perhaps with a ticket number. - Protect
main: require a pull request, review and passing checks, and block force-pushes (GitHub branch protection). - Write meaningful commit messages: start with a short summary line in the imperative ("Add login form").
- Delete merged branches to avoid clutter.
- Tag releases (
git tag v1.2.0) so you can find exactly what shipped. - Do not commit secrets. A leaked key stays in history even after you delete the file.
- Resolve conflicts calmly: read both versions, keep what is right, run tests, then commit.
Hotfix: how to fix production fast
With GitHub Flow or trunk-based, a hotfix is just a normal short branch from main, reviewed and deployed quickly. With Git Flow, a hotfix/* branch is cut from main, merged back into both main and develop. Whatever you choose, write down the hotfix process before you need it at 2 a.m.
How to choose
- Do you deploy continuously (web app, one live version)? Use GitHub Flow or trunk-based.
- Do you support several released versions or ship on a schedule? Consider Git Flow or release branches.
- Is your team new to Git? Start with GitHub Flow, protect
main, and keep pull requests small.
Takeaway: pick the simplest strategy that fits how you release, keep branches short, protect main, and choose one merge style for the whole team. Rebase your own branches only, and never rewrite shared history.
Linux & DevOps series, Part 3 (everyday operations): 4 of 5. Previous: Bash scripting basics: write safe scripts that do not surprise you.



