Skip to main content
Read the type and the scope from the branch name alone. No git log required.

Which model applies

Every repo runs one of two branch models. Check the repo’s default branch (git remote show origin or the GitHub settings page). Never guess it from whatever branch happens to be checked out locally:

git-flow branch types (repos with develop)

git-flow repos use the branch model below. Git Town starts and syncs branches. It is installed and configured globally (nix-managed git-town.* git config). Releases are cut on develop, so main is never merged back. release-please keeps one release PR open against develop with the version bump, CHANGELOG and release notes. That PR auto-merges (squash) once its checks pass, so the release commit is the last release-related commit on develop. Commits that land after it ship in the same promotion and take the next version. The tag and GitHub Release are created when main receives the promotion, never earlier. See PR conventions for the full merge-strategy breakdown.

Promotion is a step you take, not an event that happens

Feature PRs squash into develop, then stop there. Nothing on develop reaches production until someone opens a develop → main promotion PR and lands it with a merge commit (never squash, never rebase). Each promotion merge publishes the release already prepared on develop. This is the step that gets skipped. Features pile up on develop, and everything looks “done” in the PR list. Meanwhile main, the branch releases are cut from, quietly falls weeks behind. Treat promotion as part of finishing work, not a separate ceremony:
  • When a change on develop is worth having in production, promote. Small, frequent promotions beat batched ones. Each release stays reviewable and revertible.
  • Before calling a stretch of work complete, check git log origin/main..origin/develop. Commits listed there are merged into develop, not shipped.
  • The promotion PR needs no new content. Its body is the list of squashed feature commits it carries.

Working a change

  1. Create or switch to a fresh worktree based on origin/develop.
  2. Start the branch there with git town hack <name>, or let the harness worktree create it. Git Town fetches first and bases the branch on the fresh develop tip. It adds no prefix, so <name> is the full branch name, for example git town hack fix/123-inventory-loader. Start it with the short type of the eventual commit. Lead the slug with the GitHub issue number when one exists. Never invent an issue number.
  3. Commit atomically: one fix, one feature, or one coherent section of updates per commit. Follow Commit conventions and reference the issue (#123), using a closing keyword (fixes #123) when the change closes it.
  4. Open the PR against develop. Squash-merge ordinary feature PRs. Use a merge commit into develop only where preserving multiple atomic commits matters: stacked work, coordinated multi-commit changes.
  5. hotfix/* is the one exception to PR targeting: it branches from main, its PR targets main directly with a merge commit (never squash), and a second PR from the hotfix branch (not from main) carries the fix into develop.
  6. Small direct pushes to develop are fine; open a PR for anything reviewable.

Git Town usage

  • Start: git town hack <name>. Never branch from a local main or develop. The agent hooks deny checkout -b, switch -c and a worktree add whose start point is not origin/….
  • Sync: git town sync rebases the branch onto the fresh parent and pushes with lease. Never merge the base into a PR branch: a merge commit fails the org’s Conventional Commit ruleset.
  • Propose: git town propose opens the PR against the parent.
  • Hotfix: git worktree add --no-track -b hotfix/<name> <abs-path> origin/main.
  • Releases are not started by hand: release-please opens the release PR on develop.
  • Per-repo config: a git-flow repo commits .git-branches.toml with main = "develop" (or sets git-town.main-branch=develop). Everything else comes from the global Nix config.

Trunk-flow branch types (Conventional Branch)

Repos without a develop branch use trunk-based flow: no hotfix/* or release-please develop branch, and PRs squash-merge straight into main (see PR conventions). Branch names follow Conventional Branch: a short type prefix, a /, and a kebab-case description. The type matches the commit conventions prefix that the eventual squash commit uses, so the branch name predicts the changelog entry.

Shape

  • <type> is one of the allowed short forms below, never the long form.
  • <short-kebab-case-name> is lowercase, hyphen-separated, descriptive but tight (≤ 5 words is usually enough).
  • An issue number may lead the name (fix/123-dns-resolver-restart). No usernames, no dates.
Examples:

Allowed types: short forms only

These match the Conventional Commits prefixes one-to-one. Always the short form. feat not feature. fix not bugfix. No exceptions. If two types fit, pick the one that produces the right version bump. feat: → minor, fix: → patch, everything else → no version bump (per release-please defaults).

Why short forms

The long forms (feature, bugfix) don’t appear in any Conventional Commits / Conventional Branch spec. Mixing both creates two parallel taxonomies that release tooling, branch protection rules, and CODEOWNERS regexes have to match independently. One canonical set keeps everything (CI, automation, AI agents, humans skimming git branch) on the same page. The full Conventional Branch spec lives at conventional-branch.github.io; the commitlint config that enforces the matching commit prefixes is @commitlint/config-conventional. Its hotfix/ type matches the git-flow hotfix branch above. Trunk-flow repos use neither hotfix/ nor release/.

What this connects to

Commit conventions

The matching commit prefix vocabulary. Branch type and commit type are one decision, made once.

PR conventions

PR title inherits the conventional-commit prefix from the branch.

Git transport

SSH for public, HTTPS for private. Picks transport by visibility, not by branch.

CI/CD policy

How feat: and fix: translate into release-please version bumps once the branch merges.