The Issue

Plenty of research says using a trunk-based version control merge/branch strategy is highly effective. But what if a team isn't ready for this?

  • The trunk-based strategy expects that every commit to main is ready for production (assuming it passes all automated tests).
  • If a commit isn't ready for production, a great technique is to hide the change behind a feature flag.

Our team has this situation:

  • Commits need to be tested on demand.
  • But only some of those commits will be deployed to production right away.

For example, we may be working on a larger feature that--for various reasons--has to be released all together. For now, we cannot use feature flags and we can't easily add more discrete testing environments.

Git Flow seems like a solution, but it still expects to merge point-in-time commits into main.

Here is an approach we're trying. It maintains two independent--but related--branches: test and main. Release branches feed main.


  • No PRs to test or main without a story
  • Latest test can always be deployed to Test
  • A PR to test must be QA-ready
  • Latest main can always be deployed to Production
  • A PR to main must be production-ready

Work flow

I was tempted to use feature branches for longer-lived changes. But we already link stories to features, so using a feature branch in Git seems unnecessary.

It would also require lots of rebasing and forced pulls, the combination of which is asking for trouble.

Besides branching and merging, the flow below refers to tagging stories, moving them into columns, and triggering pipelines. We use Azure DevOps for all of this. You can ignore that stuff if you want, it's not required for the branch/merge strategy.

What matters most is we

  • PR into test
  • Cherry-pick into release
  • PR into main


  1. Developer begins coding for a story in a branch, e.g. clf/198222-improve-customer-grid-performance
  2. When the story is ready for QA:
    1. Tag story as Deployable
  3. When QA requests testing one or more stories:
    1. PR: merge clf/198222-improve-customer-grid-performance into test
    2. Tag test branch commit to trigger pipelines
    3. Remove Deployable tags
  4. When stories are fully tested
    1. Move to UAT Done
    2. If deployable to production, tag as Deployable for next release
    3. Move Deployable to Deploy Doing
    4. Branch from main to release/[datetime]. Cherry pick story commits into release branch.
    5. PR: merge release/[datetime] into main
    6. Tag main branch commit to trigger pipelines
    7. Delete story branches
    8. Delete Deployable tags
    9. Move to Deploy Done

Wrap Up

This flow seems like it will succeed in keeping both test--and especially main--branches clean until we're ready for trunk-based development.