Skip to main content

Branching for CI: trunk-based development

Continuous Integration (lesson 5.1) only works if developers actually integrate continuously — merge their work into a shared branch many times a day. But teams choose branching strategies that quietly make that impossible, and then wonder why "CI" feels painful. This lesson explains why long-lived feature branches break CI, what trunk-based development does instead, and how merge queues keep the shared branch green when many people push at once.

The shared branch and "merge hell"

Every team has a primary branch — call it main (the durable name for the line of code you ship from; older repos call it master). The question every branching strategy answers is: how long does a developer's work live away from main before it merges back?

The longer your branch lives apart from main, the more main moves on without you, and the bigger and scarier the eventual merge. This is merge hell (also called integration hell): two weeks of your changes colliding with two weeks of everyone else's, all resolved in one terrifying merge full of conflicts you can no longer reason about.

mainfeature A (2 weeks)feature B (2 weeks)

The cruel irony: the whole point of Continuous Integration is to catch integration problems early and small. Long-lived branches do the exact opposite — they hoard changes apart from main and unleash them all at once. Long-lived feature branches are fundamentally at odds with CI.

GitFlow vs trunk-based development

Two strategies anchor the spectrum:

  • GitFlow — A once-popular model with many long-lived branches: a develop branch, long-running feature/* branches, release/* branches, hotfix/* branches, and main. It's elaborate and was designed for infrequent, versioned releases (think boxed software shipped quarterly). Its long-lived branches are precisely what makes continuous integration hard, so it has fallen out of favor for cloud services that ship daily.
  • Trunk-based development (TBD) — The model that pairs with CI/CD. Everyone works off one trunk (main). Developers create short-lived branches — a day or two at most — make a small change, and merge back fast. Because branches are tiny and merge constantly, main is always close to everyone's work, and integration problems surface in minutes, not weeks.
main (trunk)short branch (1 day)short branch (1 day)short branch (½ day)

The discipline trunk-based development demands: keep changes small. A small change is easy to review, easy to test, and easy to merge before main drifts. If a change is too big to finish in a day or two, you split it — often hiding the unfinished part behind a feature flag (lesson 5.6) so incomplete code can merge to main safely without being switched on.

:::note "Short-lived" is the whole point The magic isn't the branch — it's the short life. A branch that merges in a day barely diverges from main, so there's almost nothing to integrate and almost no conflict to resolve. CI stays continuous because integration happens constantly and in tiny increments. Long branches break this no matter how good your tools are. :::

Keeping main green: merge queues

There's a subtle race condition when many people merge to a busy trunk. Your change passed CI against main as it was an hour ago — but five other PRs merged in the meantime. Your change is green in isolation, yet might break once combined with theirs. Merge enough of these and main goes red, blocking everyone.

A merge queue (also called a merge train) fixes this. Instead of merging PRs directly, you add them to a queue. The queue takes each PR in order, re-runs the full pipeline against main as it will actually be after the PRs ahead of it land, and merges only if it's still green. If a change would break the combined result, it's bounced before it pollutes main.

PRs marked 'readyMerge queue\n(orders+ re-testsagainst\nthe realmain stays GREENbounce back toauthorstill greenwould break

The payoff: main is always green, so it's always deployable — which is the bedrock requirement for Continuous Delivery/Deployment. A red main blocks the entire team; a merge queue is how busy trunks stay shippable. GitHub Actions, GitLab, and similar platforms provide merge queues natively.

:::tip Durable vs dated Trunk-based development, short-lived branches, and keeping main always-green are durable practices — they're a direct consequence of what Continuous Integration means, not a tool fashion. The specific feature names ("merge queue" vs "merge train") and which platform offers them are dated details. If a strategy keeps integrations small, frequent, and green, it's compatible with CI; if it hoards changes on long branches, it isn't. :::

Common pitfalls

  • Long-lived feature branches. The number-one CI killer. Two weeks apart from main guarantees merge hell. Split the work and merge daily instead.
  • Big-bang pull requests. A 4,000-line PR can't be reviewed well or merged safely. Small, frequent changes are the unit of trunk-based development.
  • Adopting GitFlow for a daily-shipping service. GitFlow's many long-lived branches suit infrequent versioned releases, not continuous delivery of a cloud service. Match the strategy to the cadence.
  • No merge queue on a busy trunk. Without one, "green in isolation" changes combine into a red main and block everyone. The queue re-tests against the real future state.
  • Treating feature flags as optional. They're what let you merge unfinished work to main safely, which is what keeps branches short. Skipping them pushes you back toward long branches.

Why it matters

Continuous Integration only happens if people integrate continuously, and long-lived feature branches make that impossible — they hoard changes apart from main and detonate them in one merge-hell collision, the exact opposite of CI's "early and small" promise. Trunk-based development is the antidote: everyone works off one trunk via short-lived branches that merge within a day or two, keeping integrations tiny and constant, with feature flags hiding unfinished work so it can still merge safely. Merge queues keep a busy trunk green by re-testing each change against the real future main before it lands — and an always-green main is exactly what makes Continuous Delivery possible. With a green, deployable trunk producing changes, the next question is what we build from them and how we package it safely.

Next: Artifacts, registries & supply-chain security →