Skip to main content

Branch Strategy

Publica.la's standard for all GitHub-hosted repos: trunk-based development on main. No dev branch. No develop branch. One long-lived integration branch, period.

This is a decision, not a preference. The audit behind it is summarized below so future-you (or a new hire) can tell whether conditions have changed enough to revisit it.

The rule​

  • Feature branches (feature/*, fix/*, chore/*, etc.) target main via pull request.
  • main is always deployable. Merging triggers an automatic staging deploy.
  • Production deploys are gated by the production GitHub Environment (Core Team reviewer).
  • Preview deploys of a feature branch to staging happen on-demand via the staging-review approval gate on the PR. See Deploy Approval Pattern.
  • Short-lived release or integration branches are permitted by exception (see Exceptions); they are not a standing convention.

Why not a dev branch​

The dev (or develop) branch from Git Flow was designed to hold integrated-but-unreleased work for batched version cuts. Publica.la's SaaS services do not version, do not batch, and do not cut releases. In the 90 days preceding this decision (audit date 2026-04-17), across farfalla, coniglio, and medusa:

Signalfarfallaconigliomedusa
Unshipped commits on dev at audit time000
Commits on main (90d)1,848153216
Merges dev β†’ main (90d)2062829
Merges into dev (90d)1923925
Ratio (promoted : integrated)1.070.721.16
Median lag dev β†’ main~10hhourshours
Direct-to-main / hotfix merges (90d)04 chores2 chores
Tags, CHANGELOG, Releasesnonenonenone
Release-day ritual (cron, README, etc.)nonenonenone
Feature-flag infrastructurestalenonenone

dev held zero unshipped work across all three repos. The ratio of promotions-to-integrations is ~1:1: every commit merged into dev gets forwarded to main within hours. There is no release cycle for the ratio to serve. The branch was acting as a tollbooth (a merge commit, a CI pass, a staging bounce), not a reservoir (nothing accumulated).

The hidden value people sometimes attribute to a staging branch (catching bugs after merge to dev but before promotion to main) was explicitly checked: zero incidents of this kind in the audited window.

Separation of integration policy from deployment policy​

Branches should not carry environment meaning when environments already have their own gates. main is an integration branch. staging and production are GitHub Environments with their own protection rules and reviewers. Using a second long-lived branch to approximate a second environment duplicates that machinery and produces the deadlocks documented in Deploy Approval Pattern.

What replaces the dev bounce​

What dev was doingReplacement
Triggering an auto-staging deploy on mergeAuto-staging deploy on merge to main
Preview-deploying a feature for integration testingstaging-review approval gate on the PR
Keeping incomplete work out of productionproduction environment reviewer gate + feature flags as needed
A spot to "park" risky workFeature flags (Laravel Pennant recommended when needed)
A ritual signal that something is "ready for release"PR review + staging smoke tests + production reviewer approval

Exceptions​

Short-lived release/* or integration/* branches are allowed in specific cases:

  • Coordinated multi-feature ship. Two or more PRs must land on production together after joint validation. Open a short-lived branch, merge the PRs into it, deploy to staging from it, then fast-forward to main. Delete the branch.
  • Freeze window around a major event. Temporary stabilization period. Same mechanics as above; the branch dissolves when the window ends.

These are tactical, not architectural. They never outlive the work that justified them.

Things that are not exceptions:

  • "I want a place to merge things while they stabilize." Use a feature flag.
  • "We've always had dev." Not a reason. The audit above is.
  • "Different teams need different mainlines." Split the repo.

Feature flags​

Trunk-based development is usually paired with a flag system for hiding in-flight user-visible work. Publica.la does not currently have load-bearing flag infrastructure, and the audit shows we have not been using branches for this either. We are not making flags a prerequisite for trunk-based.

When a change genuinely needs to ship half-baked (dark launch, progressive rollout, per-tenant toggle), adopt Laravel Pennant for that specific need. Don't retrofit flags across the codebase speculatively; that produces dead flag debt.

Rollback​

With trunk-based, production rollback is git revert + merge + redeploy. The production reviewer gate provides the safety valve before the bad deploy, and criceto provides the smoke test after. This is unchanged from the dev-based world; dev was not functioning as a rollback buffer in the audited data.

Branches across repos (at time of writing)​

RepoTrunkStatus
farfalla-integrationsmain onlyAlready trunk-based
farfalla-https-guardmain onlyAlready trunk-based
conigliomain + devdev removal pending GitHub-migration completion (see below)
medusamain + devdev removal pending GitHub-migration completion (see below)
farfallamaster + dev (GitLab)Migrate to GitHub first; drop dev afterward, alongside the rest
Sequencing

The order is finish the GitLab β†’ GitHub migration first, drop dev second. Running the cutover runbook on coniglio or medusa ahead of the broader migration creates a second window of churn for the team to track and buys nothing operationally. Hold the cutover until every active repo is on GitHub, then sweep the dev-bearing repos one at a time.

X

Graph View