Skip to content
quarry Artifact [d7d75da8cb]
Login

Artifact [d7d75da8cb]

Artifact d7d75da8cb0faa952d2b376d887d1bfea0967b7308814a8ebcd39c10ec1be5f5:


Summary

A stack is a chain of dependent landings: each segment targets the branch below it, and they land bottom-up. quarry renders the chain, refuses accept on anything that is not the bottom, and flags a segment whose base was abandoned.

The name is strata — layers deposited in order, each resting on the one below, which is what the data already is.

No new index and no new artifact. quarry computes landings as a reading of forum posts (docs/landings.md); a stack is a reading of those same landings. The edge is already there: a landing whose target equals another open landing's branch is a stack edge. Nothing needs a depends: field.

Why this is small here

Every stacked-PR tool in git automates a rebase. GitHub's native stacked PRs (public preview 2026-07-30) rebase and retarget the whole stack server-side when the bottom merges. That is the product and also the cost: force-pushes, comments stranded as "outdated", cascading conflicts, CI churn.

zit cannot rebase, and does not need to. When a segment lands as a merge, the segment above it still has the lower tip as an ancestor, so a later three-way sees only its own delta. Measured against zit trunk 944ee007, including the case that forces a restack in git — two segments editing the same file:

=== after A landed, trunk shared.txt ===
A-EDIT / line2 / line3 / line4 / line5
=== featB -> trunk probe (post-land, same file) ===
merges   clean

So the forge's "restack" is not a history operation. It is one metadata write per child, and zit#144 puts it behind landing accept. quarry's job is to show the chain and to refuse the out-of-order action.

This also means the comment problem never arises. A landing names a branch, not a frozen range, and comments are replies in its thread — so there is nothing commit-anchored for a restack to detach. That is a property worth stating because it is the single loudest complaint about stacked PRs elsewhere.

What quarry adds

The chain, on the landings page and the record page

A landing that is part of a stack renders its position: what it sits on, what sits on it, and how deep the stack is. The list view groups a stack under its base rather than interleaving segments with unrelated landings.

Derived, not stored: follow target → branch across open landings. A cycle is possible to express and must be handled as a rendering fault, not a crash — report the chain as broken and name the segments involved.

Rendering the chain — the proposal

Written after the first real strata on this forge (RFC 0007, five segments) landed in the list as five open landings that happened to name each other. Nothing said they were one thing. A reader saw 5 Open, five rows in opened-order, and had to read the branch chips to reconstruct that protect-5 → protect-4 → … → trunk was a stack with exactly one landable end. That is the failure this section exists to name: a strata that looks like five independent landings is worse than no strata feature, because the list actively misleads about what can be acted on.

What the field does

Three products have solved this, in three shapes, and the shapes agree more than they differ.

The common ground: a position on every member (n/N), a map on the record page with the current layer marked and one-click navigation, and one action at the landable end. Where they differ is the list — GitHub and Gerrit keep rows and add a badge; Graphite groups.

The proposal

quarry groups, because its list is a queue and a queue that shows five rows for one decision counts wrong. The 5 Open chip in the screenshot is the tell: there was one thing to decide, not five.

In the list. A stack is one row, drawn at the position its base would take and carrying the base's title, with a strata mark beside the state glyph and a summary line: strata · 5 segments · protect-1 → trunk landable. The row expands (a <details>, no script) to the segments, bottom-up, each with its position badge 1/5 … 5/5, its own branch chips, and its own state glyph, so a segment already accepted (the stack is being walked) reads as done while the rest wait. Not a separate "Strata" section: the queue stays one queue, and a stack is a member of it that happens to be tall. The count chip counts decisions, so a stack of five counts one.

On the record. A stack map above the fold, the GitHub shape: every segment in order, the current one marked, each with state and a link; the landable segment named as such. Below the bottom the accept control is disabled with the reason, as the RFC already says; the map is why the reason makes sense at a glance. A segment whose base was rejected shows the map with the broken rung marked — §An abandoned base is flagged, drawn rather than only said.

On the landings page filter. No new chip for now. Open already means "awaits a decision", and a grouped stack is one open decision. If stacks turn out to need their own listing, the chip is Strata beside All, and it filters to grouped rows — a listing question, not a model one, and not this layer's.

What makes it a strata, mechanically. Unchanged from above: follow target → branch across open landings. A landing whose target is the branch of another open landing is a segment; the one whose target is not is the base. Position is depth from the base. A cycle is a rendering fault and the row says broken chain and names the members. A settled segment leaves the chain when the next one is retargeted (zit#144), which is what makes "1 of 5 landable" move up the stack as it is walked.

Not proposed here

Land the whole strata — Gerrit's submit whole topic. zit accepts one segment at a time and retargets the next; quarry could sequence five accepts bottom-up when the viewer may land every one of them. Real, wanted, and a separate change: it is an action with a failure mode in the middle (segment 3 conflicts after 1 and 2 landed), and that failure mode needs its own design before the button exists.

Build order

phase work
1 landing.list derives chains: base, position, depth, broken; the count chip counts decisions
2 the grouped row with <details> segments and position badges
3 the stack map on the record page; rejected base drawn
4 harness: a five-segment stack lists as one row, expands to five, counts one; accepting the base moves the landable mark

Accept is disabled above the bottom

Only the bottom of a stack is acceptable. quarry disables the control and states the reason, in exactly the posture it already uses for a conflicted landing: a control that would fail is not offered.

The refusal is zit's too (zit#144), so the rule holds for a CLI accept as well. quarry disabling the button is the courtesy; the guard is upstream.

An abandoned base is flagged

Reject the bottom and every segment above it is stacked on work nobody will land. Those segments still report merges clean against their target, which is true and useless. The record page says the base was rejected and names it.

Events

Per RFC 0005's noun-and-action vocabulary, the stack needs no new nouns. A retarget is a change to an existing landing, so it rides landing.updated; opening and settling already have events. A pipeline that wants "the stack advanced" subscribes to landing.accepted on the base and reads the chain.

Adding landing.retargeted is tempting and probably wrong: it describes a mechanism, not a thing that happened to a repository, and RFC 0005's test for a noun is that a person would recognise it.

Capabilities

Unchanged. Accept still requires fossil's i (Write/check-in), because it writes a merge check-in. Retarget writes a forum reply, so it requires 3 (WrForum) like every other landing transition. No new capability letter, which matters — RFC 0004 §4 is explicit that inventing one is quarry policy wearing fossil's clothes.

What this is not

Open questions

The cost worth stating

fossil never squashes and never rebases, so a deep stack leaves every intermediate merge in permanent history — landing merges plus a restack merge per revision of each base. Trunk stays correct and becomes visibly braided.

That is the trade this model makes: noisier history in exchange for never rewriting, never force-pushing, and never stranding a review comment. It is the right side of it, but the cost grows with depth in a way git's does not, and history legibility — not correctness — is where a complaint would surface first.