tbd
Trunk-based development over git
tbd is a small, opinionated wrapper over git’s DAG. It exists to enforce one
invariant on every mutating operation: the head of the trunk must be an
ancestor of whatever you operate on or produce. That single rule
(git merge-base --is-ancestor) is what keeps trunk-based development safe. You
never integrate, release, or deploy work that has silently diverged from trunk.
The current release is v2.3.4. Its resumable release saga and typed workflow core now consume the released Go+ stdlib v0.207.0.
When your branch has diverged, tbd rebases it onto the latest trunk for you and shows you the move as a before/after graph. Nothing happens silently.
New here? Run tbd learn for a narrated tour of the whole workflow, built
around two developers contending for a shared deploy lease.
Features
- One commit per feature.
tbd commitdoes the same three things every time: stage all changes and collapse the feature to exactly one commit, fetch the trunk, and rebase that commit onto the latest trunk head. The feature is always one commit sitting directly on top of trunk. - Feature branches that stay honest.
tbd feature startforks from the latest trunk;tbd feature pushpublishes the branch for review with force-with-lease;tbd feature finishrebases onto trunk, fast-forwards trunk (never a merge commit), pushes, and cleans up. Divergence triggers a visible auto-rebase, or a refusal if you turn auto-rebase off. - Releases cut straight from trunk, as a branch, a tag, or both, configured per repository. A release can only be cut from a commit that is on trunk.
- Leases for deployment. A lease is the tag your CD pipeline watches.
tbd lease <name>decides the move at the DAG level: bootstrap an unset tag to trunk head, advance it to your latest commit when it sits on an earlier (even pre-amend, via the reflog) commit of your branch, or take it when it is on someone else’s branch. Every move is compare-and-swap, so two people cannot grab the same deploy slot in one race. The honest part: git has no native lock, so a lease guarantees a single winner per move, not a held lock across the run. tbd does not pretend otherwise. Pick the mechanism withlease-strategy:tag(above),ephemeral-branch(a deploy branch that exists only while leased, blown away and remade at your tip on every lease), ornone. - A guard for CI.
tbd guardexits 0 when the invariant holds and 1 when it does not.
Configuration
A .tbd.yaml in the repo root tunes the workflow:
trunk-name: develop
feature-prefix: feature/
release-strategy: branch # "branch" | "tag" | [branch, tag]
release-branch-prefix: release/
lease-tags: [dev-deploy, uat1-deploy, uat2-deploy]
auto-rebase: true # false = refuse on divergence instead
tag-push: with-lease # "with-lease" (CAS) | "force"
The rebase, made legible
before after
* trunk advances (trunk head) * widget work (replayed)
| * trunk advances (trunk head)
| o widget work (feature) |
|/ o fork point
o fork point
Built as part of the GoForge suite, in Go, with the git CLI and nothing else.