Skip to main content

0001. Two-branch model with tag-driven releases

Context and problem statement

HAI-Co² has two deploy targets: a dev environment (always tracking the latest integration) and a production environment (only the curated, stable release). The flow from a contributor’s first PR to a running production build needs to be obvious to outsiders, reproducible (so any tagged version can be rebuilt and redeployed), and machine-enforceable in CI. GitHub Flow (one default branch + ephemeral feature branches) is the popular default, but it conflates integration with release: every push to the main branch is potentially a release. GitFlow, conversely, prescribes five branch types and is heavier than this project needs.

Decision drivers

  • Single tag → reproducible image → exact deploy. No “rebuild on demand.”
  • A reviewer should be able to look at one diff (develop vs. main) and see what is about to ship to production.
  • The CI workflow must be one file, not a fork-per-environment.
  • Public contributors must be able to land changes without coordinating with a release manager.

Considered options

  1. GitHub Flow: single main, ephemeral branches, deploy from main.
  2. GitFlow: develop + main + release/* + hotfix/* + feature/*.
  3. Trunk-based with environment tags only: single main, deploy by tag.
  4. Two-branch model: develop + main, tag-driven releases. PRs land on develop; promotion to main is a fast-forward + vX.Y.Z tag.

Decision outcome

Chosen option: two-branch model with tag-driven releases, because it preserves a clear “what is about to ship” diff (develop vs. main) without the ceremony of GitFlow’s full branch set. Tag scheme: The single CI workflow drives both. Tag names must be strict SemVer; anything else is rejected by resolve-release.

Positive consequences

  • One PR = one diff against develop. Predictable.
  • main is always exactly what is in production. git log main is the release history.
  • Tags are immutable; production deploys are byte-equivalent to dev deploys for the same SHA.
  • Public contributors only need to know one rule: target develop.

Negative consequences

  • Two long-lived branches require an explicit promotion step (the develop → main fast-forward + tag push).
  • When the repo is private (free tier), the GitHub merge-button cannot enforce Require linear history, so the promotion has to be done locally. This is documented in CONTRIBUTING.md and disappears once the repo is public.

Pros and cons of the options

GitHub Flow

  • + Minimal ceremony; what most OSS projects use.
  • No staging surface; every push is a release candidate.

GitFlow

  • + Explicit release / hotfix branches.
  • Five branch types is overkill for a two-environment project.

Trunk-based + environment tags

  • + No long-lived branches.
  • No “what is about to ship” diff; harder to triage what is in prod.

Two-branch model (chosen)

  • + Predictable; documented in one short table; aligns with how dev and prod are actually deployed.
  • Requires the promotion fast-forward to stay linear.