What are "Gitflows"
GitLab flow, Gitflow, Github flow, Trunk-based flow... There are quite a few of them. They're just standardized way of using git. It's like a framework for git.
If you ever worked as a team on git, someone at some point probably set one of them so everyone would just stop pushing things on main as soon as possible. So this guy (often foundly called "gitmaster") instructed everyone to create branches, to update them, to merge them... And he probably used one of these as a reference.
First, Let's Talk About Commits
Before we dissect branching models, we need to agree on what a commit is. It's not a "snapshot" of your whole codebase, that would be incredibly inefficient.
A commit is a delta: a small package of changes representing the difference between the code before and after your edit.
export default {
data () {
return {
msg: 'Your code before'
msg: 'What your commit actually contains'
}
}
}Each commit is linked to its parent, creating a chain of deltas... Kind of like a chained list (graph theorists please don't kill me). A branch is simply a pointer to a specific commit in this chain.
Understanding this is key. A branch isn't some heavy, isolated container; it's just a lightweight label on a timeline of changes. The complexity comes from how we decide to organize these timelines.
Now, let's see how some popular models organize their chains.
Gitflow
The grand, the OG, the most famous which gave a name to the practice of "how do I get my developers to get their shit together on the repository".
It all started from a blog article by Vincent Driessen in 2010. Along with git, it took the industry by storm and gave people a framework to their new versioning tool. It really helped people migrating from SVN, using a system of branches with actual purposes, with its notable two "eternal" branches:
develop, for development and unstable codemaster, for production-ready code
Development is done through feature branches (or bugfix) checked out from develop, merged to develop. When you're ready to release, you checkout the relevant release/ branch do some testing, add some bugfix if needed, then merge to master where you'll tag for history purposes. Since the bugfix might be needed on develop, you merge that release there too.
If the tagged version needed some further fixing, you could checkout a hotfix/ branch from maina and merge it to both master and develop to get the fix everywhere.
Gitflow was revolutionary, but it was designed for a world of monolithic, infrequently-released software. In modern development, its complexity often becomes a liability. The develop branch, in particular, tends to become a chaotic dumping ground for half-finished features, making it impossible to cut a clean release.
The classic problem: you need to release features A and B, but your colleague has already merged the incomplete and buggy feature C into develop.
You're stuck. You can't release from develop without shipping the broken feature C. Your only option is to create a release branch and try to surgically revert C, a messy and error-prone process that creates more problems down the line.
The obvious solution is to ensure that every commit on the main branch is releasable. If you do that, the develop branch becomes redundant. You can simply tag any commit on main to create a release. This realization is the first step toward a simpler, more effective workflow.
GitLab flow
The idea is simple : if you have an environment, then it should have a branch. And when we want a feature on a given environment, we're going to "pour" it from main to the given environment.
let's say we have the regular develop, staging and production environments.
Sounds great on paper, you get a feature done, you deploy it to each environment successively through merge on its each "env-branch". It gives insight on what feature is deployed on each environment at a glance, your manager is going to be very happy.
You just have to follow one rule: merge must follow the same order. main > develop > staging > production. If you skip one, or if you add a single commit to a branch for "fixing" purposes... Then your commit parents won't be the same on future merges, so you'll get merge conflicts, and over time, different code bases.
If you stand by the rule, it's alright: it works, it's simple... But I find it terribly inefficient, even more so with compiled code. Deploying based on branch means rebuilding for each branch. In our scenario, it means going through the same build pipeline 4 times.
It would have been much more quicker to make the repository agnostic to its environments. You merge on main, you build once through your build pipeline to make an unique package, then your deployment pipeline allows you to deploy that package to any environment.
If you need to know where each environment is up to in terms of commit, integrated CD tools like GitLab's or Azure Devops' often have a separate page to monitor environments, which display what was deployed on it.
GitVersion would give incremental version on each commit for your build package, you could also have your deployments display a changelog... This way, you don't have to handle so many branches and you don't have to wait for your pipeline to build between each merge.
So if you remove these environment branches, you're left with just main and feature branches, which is exactly like...
Github flow
Github flow is just this, a main branch containing stable code, with feature branches created from it. This is basically the trunk-based workflow, Github's documentation just add a mandatory pull request on your feature workflow to get your code reviewed before merging.
It's as simple as your projects in college: you want to add something to main, you make your feature, you develop it, you get it reviewed, you merge it. Then you get a CI/CD pipeline to build it and deploy it anywhere.
If you're in a continuous deployment workflow, this is perfect, and enough. You have to deploy to production as soon as possible, this workflow allows it.
But the recommendations stops there. Pull request makes updating your branch mandatory, but not how their commits should be written, nor how the branch should be merged.
So you could totally have everyone merge main to their feature branch, and have main look like this:
And these loops will stay forever on main, creating a messy, unreadable "spaghetti" history. This can be cleaned up with a squash-on-merge policy, which turns each feature branch into a single, clean commit on main.
This is much better. But GitHub Flow still leaves a critical question unanswered: What if you need to support multiple versions in production?
If you need to patch version 1.0.0, you can't just add a fix to main, because that would also ship all the new features and breaking changes from version 2.0.0. You need a way to isolate the old version.
This is where the hotfix workflow from Gitflow becomes useful again. What if we could combine the simplicity of GitHub Flow with the safety of Gitflow's support branches?
And there it is. That's the foundation of the workflow this guide will teach you. It's a pragmatic, trunk-based approach that takes the best ideas from other models:
- A single, always-releasable
mainbranch (from Trunk-Based Development). - Short-lived feature branches with mandatory pull requests (from GitHub Flow).
- A clean, squashed history on
main. - Long-lived
supportbranches for hotfixes and maintenance (from Gitflow, but simplified).
This hybrid model gives you the speed and simplicity for day-to-day development, with the safety and structure needed for professional software maintenance. The following pages will show you how to put it into practice.