Git Flow vs GitHub Flow vs Trunk-Based: Which Git Workflow to Choose
Git Flow is a branching model with long-lived main and develop branches plus supporting feature, release, and hotfix branches; it suits products with scheduled versioned releases. For web services that deploy often, GitHub Flow (short-lived branches off main plus pull requests) or trunk-based development with feature flags is usually simpler.
What is a Git workflow and why does the choice matter?
A Git workflow is a team agreement about which branches exist, where they branch from, where they get merged, and what counts as ready for release. Git itself enforces none of this, so without an agreement everyone does their own thing — and a month later the repository has dozens of branches, nobody knows what is actually deployed, and merges turn into hours of conflict resolution.
The best-known models are Git Flow, GitHub Flow (a flavor of the Feature Branch Workflow), and trunk-based development. This isn't "old vs. new" — they are tools for different conditions: how often you ship, how many product versions you support at once, and how mature your automated tests are.
Git Flow: the model with develop and release branches
Git Flow was described by Vincent Driessen in 2010. The model is built around two long-lived branches:
- main (master in older projects) — released code only; every commit here corresponds to a release and is tagged;
- develop — the integration branch where finished features are merged for the next release.
Plus three types of short-lived branches:
| Branch | Branches off | Merges into | Purpose |
|---|---|---|---|
| feature/* | develop | develop | Building a new feature |
| release/* | develop | main and develop | Stabilizing a version: bug fixes, version bump, docs |
| hotfix/* | main | main and develop | Urgent fix for a released version |
Git Flow with plain Git commands
The lifecycle of one feature and one release looks like this:
# feature
git switch develop
git switch -c feature/cart-discounts
# ... commits ...
git switch develop
git merge --no-ff feature/cart-discounts
git branch -d feature/cart-discounts
# release
git switch -c release/1.4.0 develop
# ... version bump and bug fixes ...
git switch main
git merge --no-ff release/1.4.0
git tag -a v1.4.0 -m "Release 1.4.0"
git switch develop
git merge --no-ff release/1.4.0
git branch -d release/1.4.0
The --no-ff flag is essential to this model: it creates a merge commit even when a fast-forward is possible, so the history still shows which commits belonged to a given feature or release.
The git-flow extension
To avoid typing these sequences by hand, there's the git-flow tool (these days people usually install a maintained fork, often packaged as git-flow-avh). It adds subcommands:
git flow init -d
git flow feature start cart-discounts
git flow feature finish cart-discounts
git flow release start 1.4.0
git flow release finish 1.4.0
git flow hotfix start 1.4.1
git flow hotfix finish 1.4.1
git flow init asks for branch names and prefixes (-d accepts the defaults), and the finish commands perform all the required merges, create the tag, and delete the temporary branch. Many Git GUI clients also support Git Flow with buttons.
Git Flow pros and cons
Pros: a clear structure, an obvious status for the code (main is what customers have; develop is the next version), a dedicated release stabilization phase, and convenient hotfix support. It fits mobile apps, packaged software, libraries, and firmware well — anywhere a release goes through app store review or gets installed on the customer's side.
Cons: lots of branches and merges, long-lived feature branches drift away from develop, and the double merges for release and hotfix branches are easy to forget. For a web service that deploys several times a day, release branches become pure bureaucracy. In 2020 the model's author himself added a note to his original post: for continuously delivered software, consider simpler approaches such as GitHub Flow.
GitHub Flow: short-lived branches and pull requests
GitHub Flow is a lightweight version of the Feature Branch Workflow. The rules fit in a few points:
- main is always working and deployable.
- All work happens in a descriptively named branch off main.
- You push the branch regularly and open a pull request (merge request) for discussion.
- After review and a green CI run, the branch is merged into main.
- Whatever lands in main is deployed right away.
git switch main
git pull
git switch -c feature/new-payment
# ... commits with clear messages ...
git push -u origin feature/new-payment
# open a pull request, pass review and CI, merge into main
Pros: a single long-lived branch, minimal rules, a fast path from idea to production, and code review built in. Cons: there's no natural place to maintain multiple versions or stabilize before a release, and without solid automated tests "main is always deployable" is just wishful thinking.
A similar approach with extra environment branches (such as staging and production) is sometimes called GitLab Flow. It helps when deploying to production isn't instant but happens on a schedule or after manual QA.
Trunk-based development
In trunk-based development, every developer integrates changes into a single branch (the trunk, i.e. main) frequently — at least once a day. Branches, if any, live for hours rather than weeks. Unfinished functionality does land in main, but stays hidden behind a feature flag:
if (featureFlags.isEnabled("new-checkout", user)) {
renderNewCheckout();
} else {
renderOldCheckout();
}
The main advantage is that painful conflicts almost disappear: changes are small and get merged while everyone still remembers the context. The model supports continuous integration in the literal sense and works well for mature teams with fast CI and high test coverage. Release branches are allowed, but they're cut from trunk right before shipping, and fixes go into trunk first and are then carried over with cherry-pick.
Cons: it takes discipline and infrastructure — fast tests, flags, monitoring. Newcomers find committing to main every day intimidating, and weak CI turns trunk into a permanently broken branch. Flags also need to be removed on time, or the code fills up with dead conditionals.
Git Flow vs GitHub Flow vs trunk-based: comparison
| Criterion | Git Flow | GitHub Flow | Trunk-based |
|---|---|---|---|
| Long-lived branches | main and develop | main | main (trunk) |
| Feature branch lifetime | Days to weeks | Days | Hours, or no branches |
| Release cadence | Scheduled versions | On every merge | Continuous |
| Supporting multiple versions | Yes, via tags and hotfixes | Awkward | Via release branches |
| CI and test requirements | Moderate | High | Very high |
| Risk of big conflicts | High | Medium | Low |
| Learning curve | Rules to learn | Minimal | Requires culture and feature flags |
| Typical project | Mobile app, library, packaged software | Web service, open source, startup | Large product with frequent deploys |
How to choose a Git workflow for your team
Answer a few questions:
- Do you need to support several released versions? If customers are on both 2.x and 3.x and both need fixes, go with Git Flow or trunk-based with release branches.
- How often do you deploy? Once a quarter — Git Flow won't get in your way. Every day — pick GitHub Flow or trunk-based.
- How much do you trust your tests? If CI is slow and coverage is weak, start with GitHub Flow with mandatory review and gradually shorten your branches.
- How big and experienced is the team? For a small team and for beginners, GitHub Flow is easiest: one rule — "main always works" — and a pull request for everything.
Practical advice: don't adopt Git Flow "because that's how it's done." Start with the simplest process that solves your problems and add complexity only when a concrete pain shows up — say, you need a stabilization phase or support for older versions.
Rules that work in any workflow
- Protect main on the server: no direct pushes, required reviews, and a green CI before merging.
- Agree on branch naming:
feature/,fix/,hotfix/plus the ticket number. - Keep branches short-lived: the longer a branch lives, the more painful the merge. Regularly bring main into your branch with merge or rebase.
- Pick one pull request merge strategy (merge commit, squash, or rebase) and use it consistently.
- Mark releases with annotated tags following semantic versioning.
Hotfixes in each model: one bug, three processes
The difference between the approaches is clearest when you need an urgent production fix.
In Git Flow, you create hotfix/1.4.1 off main; once fixed, the branch is merged into main with a v1.4.1 tag and, crucially, into develop as well (or into the current release branch, if there is one). Forgetting that second merge is a classic mistake: the bug "comes back" in the next release.
git switch -c hotfix/1.4.1 main
git commit -am "Fix discount calculation"
git switch main && git merge --no-ff hotfix/1.4.1
git tag -a v1.4.1 -m "Hotfix 1.4.1"
git switch develop && git merge --no-ff hotfix/1.4.1
In GitHub Flow, a hotfix is just a regular short-lived branch off main with a pull request that gets priority in review. Once merged, it ships to production through the same automated deploy — there's no separate process.
In trunk-based development, the fix is committed to trunk first, and if a release branch is running in production, the commit is carried over with git cherry-pick -x <hash>. The -x flag adds a reference to the original commit in the message so the link is visible in history. And if the problem is in a new feature, it's often enough to just turn off its feature flag without touching the code at all.
How to answer "Which Git workflow have you used?" in an interview
The interviewer isn't checking whether you know the names — they want to see that you understand the trade-offs. A good answer names the model, describes how branches and reviews worked, and explains why it fit the project. For example: "We used GitHub Flow: branches off main, pull requests with mandatory review and CI, and automatic deploys after merging. We didn't go with Git Flow because we shipped several times a week, and release branches would only have slowed us down." Bonus points for explaining what you'd change as the process scaled.
Summary
Git Flow is a strict model with develop, release, and hotfix branches, a good fit for products with scheduled versions and multiple supported releases. GitHub Flow is simpler and suits most web projects, while trunk-based development gives you maximum speed but requires mature CI and feature flags. Choose your Git workflow based on release frequency and test quality, not on what's trendy. We cover all three approaches, with their commands and common mistakes, hands-on in our Git course.
FAQ
Is Git Flow outdated?
No, but its niche has narrowed. For web services with continuous deployment it's overkill, while for mobile apps, libraries, and packaged software it's still convenient.
Do I need a develop branch if we release often?
Usually not: develop duplicates main and adds an extra merge. With frequent releases, main plus short-lived feature branches is enough.
Can I use rebase with Git Flow?
You can rebase your unpushed or personal feature branch onto develop, but in the classic model the final merges into develop and main are done with merge --no-ff to preserve the history structure.
Why is a feature flag better than a long-lived feature branch?
The code is integrated into main right away and doesn't drift from the rest of the codebase, and enabling the feature becomes a separate decision you can roll back without reverting code.
How do I migrate from Git Flow to GitHub Flow?
Finish the releases in progress, merge develop into main, reconfigure CI to deploy from main, and delete develop. From then on, create new branches only from main.
This topic is covered in depth with practice in the course “Git from Scratch to Confident Developer”.
Go to course