# GitFlow Workflow: The Traditional Branching Model, and When It Still Makes Sense

If you've worked at a company with quarterly releases, a QA team, and a "release candidate" Slack channel, you've almost certainly touched GitFlow. Vincent Driessen published it in 2010, back when most teams shipped software on a schedule rather than on every merge. It became the default answer to "how should we branch?" for about a decade.

These days it gets a lot of side-eye. Some of that is deserved. But GitFlow isn't dead, it's just often used on projects it was never meant for. Let's go through how it actually works, where it hurts, and how to run it without making your team miserable.

## The core idea

GitFlow uses two long-lived branches and three kinds of short-lived ones.

**Long-lived:**

- `main` (or `master`): always matches what's in production. Every commit here is a release, and it's tagged.
- `develop`: the integration branch. It holds the next release's work.

**Short-lived:**

- `feature/*`: branched from `develop`, merged back into `develop`.
- `release/*`: branched from `develop` when you're ready to stabilize. Only bug fixes go here. Merged into both `main` and `develop`.
- `hotfix/*`: branched from `main` to patch production fast. Merged into both `main` and `develop`.

## The flow, visualised

![](https://cdn.hashnode.com/uploads/covers/6a11b66c1a3cf7bffc297b8a/dbe30ae5-dfa4-48ec-8b03-f4a82fdb45d5.png align="center")

Notice that release and hotfix branches merge *twice*. That's the part people forget, and it's the most common source of "why is this bug back?" incidents.

## Working through it with real commands

### Starting a feature

```shell
git checkout develop
git pull origin develop
git checkout -b feature/user-profile

# ...work, commit, push...
git push -u origin feature/user-profile
```

When it's done, open a PR into `develop`. I'd recommend `--no-ff` merges here so the feature stays visible as a unit in history:

```shell
git checkout develop
git merge --no-ff feature/user-profile
git branch -d feature/user-profile
```

### Cutting a release

```shell
git checkout develop
git checkout -b release/1.4.0

# bump version, update changelog, fix last-minute bugs
git commit -am "Bump version to 1.4.0"
```

Once QA signs off:

```shell
git checkout main
git merge --no-ff release/1.4.0
git tag -a v1.4.0 -m "Release 1.4.0"

git checkout develop
git merge --no-ff release/1.4.0

git branch -d release/1.4.0
git push origin main develop --tags
```

### Hotfixing production

```shell
git checkout main
git checkout -b hotfix/1.4.1

# fix the bug
git commit -am "Fix null pointer in payment handler"

git checkout main
git merge --no-ff hotfix/1.4.1
git tag -a v1.4.1 -m "Hotfix 1.4.1"

git checkout develop
git merge --no-ff hotfix/1.4.1
git branch -d hotfix/1.4.1
```

One gotcha: if a `release/*` branch is open when you hotfix, merge the hotfix into the release branch instead of `develop`. It'll reach `develop` when the release merges back.

## Branch lifecycle at a glance

![](https://cdn.hashnode.com/uploads/covers/6a11b66c1a3cf7bffc297b8a/9a73b6ec-e1ba-43c9-871a-fbedf21219b7.png align="center")

## What GitFlow is good at

- **Scheduled, versioned releases.** Mobile apps, desktop software, firmware, on-prem products, anything where you can't just redeploy.
- **Supporting multiple versions in production.** If customers are on v2.3 and v3.1 at the same time, you need long-lived release lines.
- **Formal QA and sign-off.** The release branch gives testers a stable target while developers keep moving on `develop`.
- **Clear audit trail.** `main` plus tags gives you a clean history of exactly what shipped.

## Where it hurts

Be honest with yourself about these before you adopt it.

1. **Long-lived branches drift.** A feature branch that lives for three weeks will conflict with everything. The longer it lives, the worse the merge.
2. **Double merges are easy to botch.** Forget to merge a hotfix back into `develop` and you've re-introduced a bug.
3. **It slows down CI/CD.** If you deploy multiple times a day, `develop` → `release` → `main` is pure ceremony.
4. **Two "sources of truth."** Is `develop` the truth, or `main`? New joiners ask this constantly.
5. **Heavy process for small teams.** A team of four doesn't need five branch types.

Even Vincent Driessen added a note to his original post saying that if you're doing continuous delivery, you should probably use something simpler. Worth reading.

## Best practices if you do use it

- **Keep feature branches short.** Days, not weeks. If a feature is huge, use feature flags and merge incomplete work behind a flag.
- **Rebase feature branches on** `develop` **regularly** (`git rebase develop`) so integration pain stays small.
- **Protect** `main` **and** `develop`**.** Require PRs, reviews, and passing CI. Nobody should push directly.
- **Automate tagging and changelogs.** Tools like `semantic-release`, `standard-version`, or `git-cliff` remove the human-error part.
- **Use a consistent naming convention.** `feature/JIRA-123-short-description` pays off every time you search through branches.
- **Delete merged branches.** Turn on "auto-delete head branches" in GitHub/GitLab.
- **Automate the back-merge.** A small CI job that opens a PR from `main` to `develop` after every release or hotfix saves you from forgotten merges.
- **Consider** `git-flow` **CLI extensions** (`git flow feature start`, etc.) if your team likes commands over memorizing sequences. Just make sure everyone understands what they do underneath.

## Quick decision check

GitFlow is a reasonable fit if most of these are true:

- You ship versioned releases, not continuous deploys
- You maintain more than one production version
- You have a dedicated QA/stabilization phase
- Your team is large enough that integration isolation helps

If you're nodding at fewer than two of these, look at GitHub Flow or Trunk-Based Development instead.

## Wrapping up

GitFlow solves a real problem: coordinating work when releases are infrequent, expensive, and need stabilization. It just solves it with a lot of machinery. Use it deliberately, automate the boring parts, and don't be afraid to move off it when your release cadence changes.

---

If this helped you, I'd really appreciate your support. You can buy me a coffee here: [**buymeacoffee.com/vatul16**](https://buymeacoffee.com/vatul16) ☕

Thanks for reading!
