mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-09 10:18:32 +01:00
feat(ship-queue): land --pin merges a READY PR at its own green head under merge: direct (GSTA-258 L2 PR 1)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
f8085aa0e4
commit
60e5369daa
11 files changed
+578
-5
No files matched your search
@@ -271,6 +271,33 @@ the same product diff; any other rebase is a full /ship
|
||||
([ship-policy.md](ship-policy.md#receipt-carry)). A controller never asks a lane to
|
||||
rebase a reviewed branch.
|
||||
|
||||
## Direct land (`merge: direct`, gstack's default)
|
||||
|
||||
When the base policy sets `"merge": "direct"`, there are no trains, slot calls or stamp
|
||||
commits. Each PR merges at its own green head, and versions are set at release
|
||||
([ship-policy.md](ship-policy.md#land-direct)):
|
||||
|
||||
1. Lane: /ship, then `gstack-ship-queue ready --pr <n> --no-screen` at the pinned head,
|
||||
then READY to the Captain with that pin. The PR carries a `changelog.d` fragment and
|
||||
leaves VERSION, the `package.json` version and CHANGELOG.md alone.
|
||||
2. Captain: `gstack-ship-queue land --pr <n> --pin <sha>`. It merges with
|
||||
`--match-head-commit`, checks main's tree and writes a land note.
|
||||
3. Release: the release gate stamps the version and folds the fragments.
|
||||
|
||||
What to do with each refusal (nothing was merged unless the code says so):
|
||||
|
||||
| Code | Who acts | Action |
|
||||
|---|---|---|
|
||||
| `QUEUE_LAND_STAMPED` | lane | run the printed `git checkout $(git merge-base origin/main HEAD) -- …`, keep the fragment, commit, /ship and `ready` again |
|
||||
| `QUEUE_LAND_PIN_MOVED` | lane | /ship and `ready` at the new head, new READY |
|
||||
| `QUEUE_RECEIPT_MISSING` / `QUEUE_RECEIPT_STALE` | lane | /ship at the pin |
|
||||
| `QUEUE_NOT_READY` | lane, or Captain | no READY note: `ready --pr <n> --no-screen` at the pin; `merge: train`: use the train; closed or retargeted PR: as named |
|
||||
| `QUEUE_LAND_CHECKS` | Captain, then lane | pending: `gstack-pr-wait <n> --until green` and rerun; red: back to the lane (rule 16 rerun for a flaky case); missing: dispatch the workflow on the branch |
|
||||
| `QUEUE_LAND_NOT_CLEAN` | lane, or Captain | `CONFLICTING`: the lane merges main (rule 19), /ship, `ready`; `UNKNOWN`: rerun in a minute |
|
||||
| `QUEUE_TRAIN_FROZEN` / `QUEUE_LAND_LOCKED` | Captain | finish the repair or the holder first |
|
||||
| `QUEUE_TRAIN_UNTRUSTED` | Captain | a fork head; rewrite on a crew branch |
|
||||
| `QUEUE_LAND_UNVERIFIED` | Captain | merged, but main isn't the pin on its parent: revert the stray change, report it |
|
||||
|
||||
## Merge trains
|
||||
|
||||
gstack's queue tool drives trains when the repository's `.gstack/ship-policy.json` carries
|
||||
|
||||
@@ -63,6 +63,7 @@ refuses the file (`POLICY_GSTACK_TOO_OLD`). `validate` ends with
|
||||
| `min_gstack_version` | version or `null` | `null` | The oldest gstack that may read the file; see the compatibility rule above. |
|
||||
| `docs` | `{ caps: [{ path, max_bytes }] }` or `null` | `null` | Documentation files with a byte cap. `/ship` Step 14.5 runs `gstack-ship-receipt docs-caps --base <base>` (the caps come from `origin/<base>`'s policy, never the branch's copy; `--policy <file>` is for a repository bootstrapping its policy) before the verification gate and prints one `DOCS_CAP: <path> <bytes>/<max> ok\|over` line per cap; any `over` (an absent file counts) exits 1 and blocks the ship until the file is trimmed or the cap raised on the base branch (`DOCS_AUDIT_BLOCKED`). At most 64 caps, each path contained. See [the ship receipt](#ship-receipt). Needs `min_gstack_version`. |
|
||||
| `train` | `{ gate_workflow, free_workflow, full_gate_check, record_artifact, max_size, fill_hours, screen_selection, trial_reservation_usd, land? }`, `{ gates, land, max_size, fill_hours }` or `null` | `null` | Turns on [merge trains](#run-a-merge-train), read from the base revision only. Every field is required and an unknown field is `POLICY_INVALID`: `max_size` 1 to 6, `fill_hours` above 0 and at most 24, `screen_selection` `gstack-paid` \| `none`, `trial_reservation_usd` above 0 and at most 100. A block with `gates` ([any repository](#train-gates)) takes `max_size` 1 to 5 and needs `min_gstack_version` at least `TRAIN_GATES_KEY_VERSION`. `null` keeps the per-PR queue exactly as it is. Needs `min_gstack_version`. |
|
||||
| `merge` | `train` \| `direct` | `train` | `direct` lands each READY PR at its own green head with [`land --pr <n> --pin <sha>`](#land-direct): no train and no stamp commit, and `train build` refuses. A PR then leaves VERSION, the `package.json` version and the CHANGELOG at its merge base, and the release sets them. `train` keeps merge trains. Read from the base revision only. Needs `min_gstack_version`. |
|
||||
| `base_red` | `{ setup, select, timeout_sec }` or `null` | `null` | How `/ship` Step 5 proves a failure is the base branch's own before pausing on it; see [base_red](#base-red). Read from the base revision only. Needs `min_gstack_version`. |
|
||||
| `changelog_fragments` | `{ dir, required_checks? }` or `null` | `null` | PRs carry unversioned [CHANGELOG fragments](#changelog-fragments) in `dir` (one flat, repo-relative directory, no `..`) and the merge step ([`land`](#land)) assigns the version. `required_checks` (up to 50 distinct names) are the checks [`train land`](#stamped-trains-under-changelog_fragments-train-land) requires present and green on a stamp commit. `null` keeps `[Unreleased]` sections and per-PR stamps. Needs `min_gstack_version`. |
|
||||
|
||||
@@ -1890,6 +1891,42 @@ command:
|
||||
`--dry-run` merges and stamps in a scratch worktree and prints the version and
|
||||
tree, taking no lock and pushing nothing.
|
||||
|
||||
<a id="land-direct"></a>
|
||||
### `land --pin`: merge at the PR's own green head (`merge: direct`)
|
||||
|
||||
Under `"merge": "direct"` in the base policy, the Captain merges one READY PR
|
||||
at a time with `gstack-ship-queue land --pr <n> --pin <sha>`, where `<sha>`
|
||||
is the head the READY names. There's no train, no stamp commit, no base merge
|
||||
and no extra CI round. GitHub's required checks plus `--match-head-commit`
|
||||
stay the gate. Versions aren't set per PR: a PR carries only its
|
||||
`changelog.d` fragment, VERSION stays where it is, and the release stamps the
|
||||
version and folds the fragments. Land refuses, and merges nothing, unless all
|
||||
of these hold:
|
||||
|
||||
| Check | Refusal |
|
||||
|---|---|
|
||||
| the base policy sets `merge: direct` | `QUEUE_NOT_READY` |
|
||||
| the PR is open, on the base, from this repository | `QUEUE_NOT_READY`, `QUEUE_TRAIN_UNTRUSTED` |
|
||||
| the PR head is the pin | [`QUEUE_LAND_PIN_MOVED`](troubleshooting.md#queue-land-pin-moved) |
|
||||
| the PR body's /ship receipt is bound to the pin | `QUEUE_RECEIPT_MISSING`, `QUEUE_RECEIPT_STALE` |
|
||||
| a READY note from `ready` exists at the pin | `QUEUE_NOT_READY` |
|
||||
| no freeze is active and no land lock is held | `QUEUE_TRAIN_FROZEN`, `QUEUE_LAND_LOCKED` |
|
||||
| VERSION, the `package.json` version and the CHANGELOG are unchanged since the merge base | [`QUEUE_LAND_STAMPED`](troubleshooting.md#queue-land-stamped) |
|
||||
| every check at the pin is green with none pending, and each `changelog_fragments.required_checks` entry passed there | [`QUEUE_LAND_CHECKS`](troubleshooting.md#queue-land-checks) |
|
||||
| GitHub reports the PR `MERGEABLE` and `CLEAN` | [`QUEUE_LAND_NOT_CLEAN`](troubleshooting.md#queue-land-not-clean) |
|
||||
|
||||
Release outputs (the agents digest) are checked by the Version Gate, which must
|
||||
be green at the pin: it accepts an output only when it equals what the release
|
||||
tool generates at the merge base's VERSION. If the base moved and GitHub still
|
||||
reports the PR clean, land merges it as it is. Then it runs `gh pr merge
|
||||
--squash --match-head-commit <pin>` (`--merge` under `history: merge-only`),
|
||||
reads the merge commit, and checks that its tree is the pin merged onto the
|
||||
merge commit's parent ([`QUEUE_LAND_UNVERIFIED`](troubleshooting.md#queue-land-unverified)
|
||||
if not). Last, it writes a `land` note on `refs/notes/gstack-trains`. A rerun
|
||||
after GitHub merged goes straight to the tree check. `--dry-run` runs every
|
||||
check and merges nothing. Under `merge: direct`, `train build` refuses and
|
||||
names this command.
|
||||
|
||||
<a id="migrating-to-fragments"></a>
|
||||
### Migrating to fragments
|
||||
|
||||
|
||||
@@ -5073,6 +5073,59 @@ the two allowed restamps, so `land` stopped instead of restamping again.
|
||||
it), then rerun `gstack-ship-queue land --pr <n>`; the lock was released and
|
||||
the rerun restamps from the pin.
|
||||
|
||||
<a id="queue-land-stamped"></a>
|
||||
### `QUEUE_LAND_STAMPED`
|
||||
|
||||
**Meaning.** Under `merge: direct`, the PR changes VERSION, the
|
||||
`package.json` version or the CHANGELOG. Versions and the CHANGELOG are set at
|
||||
release, so a PR carries only its `changelog.d` fragment. Nothing was merged.
|
||||
|
||||
**Fix.** The lane runs the command the refusal prints, for example
|
||||
`git checkout $(git merge-base origin/main HEAD) -- VERSION CHANGELOG.md`
|
||||
(and sets the `package.json` version back by hand when it's named), keeps or
|
||||
adds the fragment with the entry text, commits, then runs /ship and
|
||||
`gstack-ship-queue ready --pr <n> --no-screen` at the new head.
|
||||
|
||||
<a id="queue-land-pin-moved"></a>
|
||||
### `QUEUE_LAND_PIN_MOVED`
|
||||
|
||||
**Meaning.** The PR head isn't the pin `land --pin` was given, so the READY
|
||||
doesn't describe what would merge. Nothing was merged.
|
||||
|
||||
**Fix.** The lane runs /ship and `ready --pr <n> --no-screen` at the new head
|
||||
and sends a new READY; land that pin.
|
||||
|
||||
<a id="queue-land-checks"></a>
|
||||
### `QUEUE_LAND_CHECKS`
|
||||
|
||||
**Meaning.** A check at the pin is red or pending, or a required check never
|
||||
ran there. Nothing was merged.
|
||||
|
||||
**Fix.** Pending: `gstack-pr-wait <n> --until green`, then rerun land. Red:
|
||||
the lane fixes it at a new head (a flaky case gets its one rerun under rule
|
||||
16). Missing: dispatch that workflow on the branch, then rerun land.
|
||||
|
||||
<a id="queue-land-not-clean"></a>
|
||||
### `QUEUE_LAND_NOT_CLEAN`
|
||||
|
||||
**Meaning.** GitHub doesn't report the PR `MERGEABLE` and `CLEAN`. Nothing
|
||||
was merged.
|
||||
|
||||
**Fix.** `CONFLICTING`: the lane merges the base into its branch, resolves,
|
||||
runs /ship and `ready` at the new head. `UNKNOWN`: GitHub is still
|
||||
computing, so rerun land in a minute. `BLOCKED` or `BEHIND`: meet the branch
|
||||
rule it names.
|
||||
|
||||
<a id="queue-land-unverified"></a>
|
||||
### `QUEUE_LAND_UNVERIFIED`
|
||||
|
||||
**Meaning.** GitHub merged the PR, but the base branch's merge commit tree
|
||||
isn't the pin merged onto that commit's parent, so something besides the
|
||||
reviewed pin reached main. No land note was written.
|
||||
|
||||
**Fix.** Compare the merge commit with `git merge-tree --write-tree <parent>
|
||||
<pin>`, revert what doesn't belong to the PR, and report it to the Captain.
|
||||
|
||||
<a id="queue-train-stamp-mismatch"></a>
|
||||
### `QUEUE_TRAIN_STAMP_MISMATCH`
|
||||
|
||||
|
||||
Reference in new issue
Block a user