Skip to main content
A stack is a chain of branches, each built on the one below it. Instead of one large change request, you send several small ones that review and land in order — and rbs keeps them sitting on top of each other while they do.
rbs records each branch’s parent in git itself (under refs/branch-metadata/), not in a service or a local database. That has three consequences you’ll feel every day:
  • A clone carries its stacks. Push a stack and any teammate, CI runner, or branch node reconstructs the same graph — no account, no extra server round-trip.
  • The whole stack survives you switching machinesrbs stack get rebuilds it from the refs alone.
  • Your teammates and the control plane see the same graph you do, because rbs stack submit pushes the metadata along with the branches.
rbs st is an alias for rbs stack, and the common subcommands have short forms too: rbs st c (create), rbs st m (modify), rbs st l (log), rbs st co (checkout).

Set up once per clone

This records which branch is trunk (guessed from the remote’s HEAD when you don’t name one) and, optionally, which control-plane project this repository maps to so that rbs stack submit can open change requests:

The daily loop

A worked example

Say you’re adding an HPC dashboard, and the work naturally splits into a module, a dashboard that uses it, and an SSH client on top.
1

Build the stack, one small branch at a time

Each create branches off the branch you’re standing on, so the three branches form a chain over trunk. rbs stack log draws it; rbs stack ls lists it one line per branch.
2

Submit the whole stack for review

Bottom-up, every branch is pushed and gets its own change request — each one targeting the branch below it, not trunk. That is what makes each request one small reviewable diff: the top branch shows only its own change, not the sum of all three. rbs stack diff shows you the same parent-relative diff a reviewer sees.
3

Respond to review feedback on a lower branch

A reviewer asks for a change in the HPC module — the bottom branch. Make the fix anywhere in the stack, stage it, and let rbs place it:
absorb figures out which downstack branch introduced the lines each staged hunk touches, amends that branch, and restacks everything above it. The feedback lands in the change request being reviewed instead of as a “fix review comments” commit on top of the stack. A hunk that could belong to more than one branch stays staged, with the reason listed.Alternatively, if you know exactly where a fix belongs:
4

Land from the bottom, then sync

The module’s change request merges. Now:
sync fast-forwards trunk from the remote, refreshes what each branch’s change request says, deletes branches whose changes are already in trunk (reparenting whatever was stacked on them), and restacks the rest. The dashboard branch now sits directly on trunk; the SSH client sits on the dashboard. Repeat until the stack is empty.A branch counts as landed when its content is in trunk — squash- and rebase-merges included, which plain commit ancestry would miss.

Keeping a stack healthy

Amending: modify

modify always restacks the branches above the one you amended. That is not optional: leaving the branches above pointing at a commit that no longer exists is how a stack rots.

Conflicts: continue and abort

A restack that hits a conflict stops and tells you. Resolve the files in your editor — even hours later; the paused restack survives the process ending — then:

Mistakes: undo

undo never deletes commits — branches created since are kept, just untracked. Running undo again redoes.

Testing every branch: test

This checks out each branch in scope in turn and runs the command there — the way to ask “does every change request in this stack pass on its own?”. The branch you started on is restored afterwards.

Scope flags

Most stack-wide commands (submit, restack, test) take the same four scope flags:

Full command surface

Two worth calling out:
  • rbs stack get <branch> fetches a branch and every branch below it, reconstructing the stack from the metadata the submitter pushed. If your local copy of a branch has truly diverged from the remote, get refuses unless you pick --rebase (replay your commits on top) or --overwrite (discard your copy).
  • rbs stack log --json emits the stack as a document, so scripts and agents can read the graph without scraping the drawing.

What submit does, precisely

For each branch in scope, bottom-up, rbs stack submit:
  1. pushes the branch (with a lease — a restacked branch has rewritten history by definition, but rbs will not clobber remote work it hasn’t seen unless you pass --force),
  2. opens or updates a ReasonOS change request targeting the branch below it,
  3. records the request number in the branch’s metadata,
then pushes the metadata so everyone sees the same graph. Useful variants:
Every branch must be restacked before submitting: a change request whose base has moved shows a diff nobody wrote. Two things to know:
  • submit needs a control plane to open change requests in. Configure one with rbs stack init --control-plane <url> --project <name>, or set RBS_CONTROL_PLANE_URL and RBS_PROJECT_NAME.
  • By default submit applies downstack — this branch and everything below it. Pass --stack to submit the whole stack from anywhere in it.

Stacks and branch nodes

ReasonOS runs one server per branch: each active branch gets its own branch node, and the editor connects to it. A stack is therefore a chain of workspaces, not just a chain of refs — and because the stack graph lives in git, every node in the chain (and the control plane, which reads the same refs from its mirror) sees the same picture without any extra coordination. Creating a stack with rbs stack create is also how you get a node per reviewable unit of work: small branches mean small diffs and focused workspaces.