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:
- Default branch
develop→ the repo is on git-flow. Read git-flow branch types below. - Default branch
main→ the repo uses trunk flow. Read trunk-flow branch types below.
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 intodevelop, 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
developis 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 intodevelop, not shipped. - The promotion PR needs no new content. Its body is the list of squashed feature commits it carries.
Working a change
- Create or switch to a fresh worktree based on
origin/develop. - 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 freshdeveloptip. It adds no prefix, so<name>is the full branch name, for examplegit 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. - 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. - Open the PR against
develop. Squash-merge ordinary feature PRs. Use a merge commit intodeveloponly where preserving multiple atomic commits matters: stacked work, coordinated multi-commit changes. hotfix/*is the one exception to PR targeting: it branches frommain, its PR targetsmaindirectly with a merge commit (never squash), and a second PR from the hotfix branch (not frommain) carries the fix intodevelop.- Small direct pushes to
developare fine; open a PR for anything reviewable.
Git Town usage
- Start:
git town hack <name>. Never branch from a localmainordevelop. The agent hooks denycheckout -b,switch -cand a worktree add whose start point is notorigin/…. - Sync:
git town syncrebases 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 proposeopens 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.tomlwithmain = "develop"(or setsgit-town.main-branch=develop). Everything else comes from the global Nix config.
Trunk-flow branch types (Conventional Branch)
Repos without adevelop 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.
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.