Signature-preserving stacks¶
Stacked work with fork-only remotes, required signatures, and a fast-forward merge strategy — without rebase-on-land or Graphite.
Model¶
Every stack PR targets upstream/main (or origin/main when there is no
upstream). Diffs are cumulative from trunk through that layer. The stack map
in each PR body is the navigation surface; GitHub base-branch chaining is not
used (parent branches cannot live on the org repo under fork-only).
Landing is always a fast-forward merge: a ref move of main to the PR head,
so the signed commits land byte-for-byte. Merging a higher layer lands every
signed commit below it; lower PRs close when their tips become ancestors of
main. Stacks need no special support on the landing side — any fast-forward
mechanism works.
Commands¶
dotty git start feat-api # branch off trunk; new stack
# … commits (resign if they were agent-unsigned) …
dotty git append feat-ui # child of tip
dotty git status # status of the stack vs trunk
dotty git propose # PRs for trunk..current
dotty git propose --all # every layer
dotty git sync # cleanup merged; refresh maps; rebase+resign if diverged
# If you never ran start/append but already have a local chain
# main ← feat-a ← feat-b (3+ nodes), `dotty git status` discovers it and
# writes lineage into git config. A lone branch off main is not a stack.
dotty git up [n] # toward tip
dotty git down [n] # toward trunk
dotty git switch # fuzzy pick a layer (alias: stack)
dotty git browse # open upstream (else origin) in a browser
dotty git resign main # re-sign after agent work or post-rebase
propose¶
- Pushes to
origin(fork). - Opens/updates PRs against trunk on the base remote.
- Multi-commit layers: pick which commit supplies title + body.
- Body layout: stack map (dotty-owned markers) then
---then commit body.
Default scope is current layer down to trunk. Mid-stack propose does not
open unfinished upper layers unless --all.
sync¶
- Fetch trunk and origin.
- Layers whose tips are already on trunk: drop from lineage; by default
delete local + origin branches (
git config dotty.stack.cleanup falseto keep them). - If any open layer diverged from trunk: prompt, then rebase the whole open
stack bottom→tip and re-sign each layer. Conflicts:
dotty git sync --continueor--abort. - Refresh stack maps on open PRs (merged rows checked off / removed).
Silent rebase without resign is forbidden — unsigned tips cannot land under
required_signatures.
Land¶
Land stack PRs with a true fast-forward. GitHub's built-in merge methods
(merge commit, squash, rebase) all create new commits, which breaks the chain's
signatures — so the repository needs some fast-forward mechanism: a bot or
GitHub Action that moves main to the PR head, or a maintainer pushing the
fast-forward manually.
One implementation is the
ff-merge GitHub Action, where
you comment /merge on a ready PR or arm /auto-merge to land it when checks
pass.
Whatever the mechanism, prefer the lowest ready layer for an incremental land, or a higher layer to land the whole chain at once.
With agent worktrees¶
dotty worktree start/ agent commits (unsigned).dotty git resign <trunk>(or layer parent) before propose.- append/propose as above.
Config¶
| Key | Default | Meaning |
|---|---|---|
dotty.stack.cleanup |
true | Delete local+origin when a layer is on trunk |