From f0d94b166e58b5b0df775e7caa87888707fda2d9 Mon Sep 17 00:00:00 2001 From: Edmund Kohlwey Date: Mon, 1 Jun 2026 12:56:33 -0400 Subject: [PATCH 1/4] integration: tip clean prunes subsumed tips When two configured tips lie on the same stack, merging the lower one into the integration branch produces no content that the higher tip's merge doesn't already produce. Keeping both wastes a merge per rebuild and inflates the resolver-prompt surface for no benefit. Add 'gs integration tip clean' (alias 'prune') that walks each tip's upstack looking for another configured tip. When one is found, the lower tip is subsumed: remove it and log which tip subsumes it. With 'low -> middle -> high' all configured, the prune cascades: 'low' is reported as subsumed by 'middle' (the nearest tip up the stack), and 'middle' as subsumed by 'high'. Both come out and only 'high' is left. A clean run with nothing to prune reports 'No subsumed tips to prune' instead of being silent. The new command is layered at the CLI: it reads tips via the existing Show, computes subsumption against the branch graph, and removes via RemoveTip. The integration handler interface stays unchanged. --- .../unreleased/Added-20260601-125454.yaml | 3 + doc/includes/cli-reference.md | 101 +++++------------- integration_tip.go | 97 +++++++++++++++++ testdata/help/gs.txt | 5 +- testdata/help/integration_tip_clean.txt | 18 ++++ testdata/script/integration_tip_clean.txt | 68 ++++++++++++ 6 files changed, 213 insertions(+), 79 deletions(-) create mode 100644 .changes/unreleased/Added-20260601-125454.yaml create mode 100644 testdata/help/integration_tip_clean.txt create mode 100644 testdata/script/integration_tip_clean.txt diff --git a/.changes/unreleased/Added-20260601-125454.yaml b/.changes/unreleased/Added-20260601-125454.yaml new file mode 100644 index 000000000..af3be418c --- /dev/null +++ b/.changes/unreleased/Added-20260601-125454.yaml @@ -0,0 +1,3 @@ +kind: Added +body: '''integration tip clean'' (alias ''prune''): Remove tips whose upstack chain already contains another configured tip.' +time: 2026-06-01T12:54:54.098874-04:00 diff --git a/doc/includes/cli-reference.md b/doc/includes/cli-reference.md index b88f330a7..d4453bb70 100644 --- a/doc/includes/cli-reference.md +++ b/doc/includes/cli-reference.md @@ -245,11 +245,9 @@ Change Requests are created or updated for all branches in the current stack. When invoked from the configured integration branch, the -"current stack" is the union of each configured tip's -downstack (the tip and the branches below it), and the -integration branch itself is pushed afterward. Branches -above a tip are deliberately left alone: they are work in -progress that has not been promoted to a tip yet. +"current stack" is the union of every tip's full stack +(upstack and downstack), and the integration branch itself +is pushed afterward. Use --dry-run to print what would be submitted without submitting it. @@ -293,48 +291,6 @@ only if there are multiple CRs in the stack. **Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.labels](/cli/config.md#spicesubmitlabels), [spice.submit.labels.addWhen](/cli/config.md#spicesubmitlabelsaddwhen), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.reviewers.addWhen](/cli/config.md#spicesubmitreviewersaddwhen), [spice.submit.skipRestackCheck](/cli/config.md#spicesubmitskiprestackcheck), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb) -### git-spice stack merge {#gs-stack-merge} - -``` -gs stack (s) merge (m) [flags] -``` - -:material-test-tube:{ title="Experimental" }[merge](/cli/experiments.md#merge) - -Merge a stack - -Merges the CRs for the current branch's stack into trunk. -Use --branch to merge a different branch's stack. - -The stack includes the selected branch, -its downstack branches down to trunk, -and every upstack branch. - -Already-merged branches are skipped automatically. -Branches must have an open Change Request to be merged. - -Before merging, the stack is checked for branches -whose base PR was already merged on the forge. -Use --no-branch-check to skip this validation. - -Before each merge, waits for CI checks to pass. -Use --build-timeout to configure the maximum wait -before failing if checks are not ready. - -By default, a branch failure skips that branch's upstack descendants, -but independent sibling branches continue. -Use --fail-fast to stop the queue after the first branch failure. - -**Flags** - -* `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. -* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. -* `--no-branch-check`: Skip stale base validation before merging. -* `--fail-fast`: Stop the merge queue after the first branch failure. -* `--branch=NAME`: Branch whose stack to merge - -**Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) - ### git-spice stack restack {#gs-stack-restack} ``` @@ -664,11 +620,11 @@ when you don't want to wait for the merge to propagate. **Flags** -* `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. -* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. +* `--branch=NAME`: Branch to start merging from * `--no-wait`: Skip polling for a single branch merge to propagate. * `--no-branch-check`: Skip stale base validation before merging. -* `--branch=NAME`: Branch to start merging from +* `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. +* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. **Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) @@ -1124,33 +1080,6 @@ Use --branch to target a different branch. * `--branch=NAME`: Branch to diff -### git-spice branch merge {#gs-branch-merge} - -``` -gs branch (b) merge (m) [flags] -``` - -:material-test-tube:{ title="Experimental" }[merge](/cli/experiments.md#merge) - -Merge a branch into trunk - -Merges the CR for the current branch into trunk. -Use --branch to merge a different branch. - -The branch must be based directly on trunk. -To merge a stacked branch, use 'gs downstack merge'. - -Before merging, waits for CI checks to pass. -Use --build-timeout to configure the maximum wait. - -**Flags** - -* `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. -* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. -* `--branch=NAME`: Branch to merge - -**Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) - ### git-spice branch submit {#gs-branch-submit} ``` @@ -1601,6 +1530,24 @@ gs integration (int) tip list (l,ls) List the configured integration tips +### git-spice integration tip clean {#gs-integration-tip-clean} + +``` +gs integration (int) tip clean (prune) +``` + +Remove tips whose upstack already contains another tip + +Removes tips whose upstack chain already contains another +configured tip. The higher tip's merge into the integration +branch captures the lower tip's content, so keeping both +costs an extra merge without changing the result. + +For each subsumed tip, the message reports which higher tip +subsumes it. A second run is a no-op once nothing remains +to prune. Existing tips with no upstack-tip relationship +are left alone. + ## Rebase ### git-spice rebase continue {#gs-rebase-continue} diff --git a/integration_tip.go b/integration_tip.go index d34bf0bf2..25fb044ec 100644 --- a/integration_tip.go +++ b/integration_tip.go @@ -5,7 +5,9 @@ import ( "fmt" "github.com/alecthomas/kong" + "go.abhg.dev/gs/internal/handler/integration" "go.abhg.dev/gs/internal/silog" + "go.abhg.dev/gs/internal/spice" "go.abhg.dev/gs/internal/text" ) @@ -13,6 +15,7 @@ type integrationTipCmd struct { Add integrationTipAddCmd `cmd:"" aliases:"a" help:"Add a branch to the integration tip list"` Remove integrationTipRemoveCmd `cmd:"" aliases:"r,rm" help:"Remove a branch from the integration tip list"` List integrationTipListCmd `cmd:"" aliases:"l,ls" help:"List the configured integration tips"` + Clean integrationTipCleanCmd `cmd:"" aliases:"prune" help:"Remove tips whose upstack already contains another tip"` } type integrationTipAddCmd struct { @@ -78,3 +81,97 @@ func (cmd *integrationTipListCmd) Run( } return nil } + +type integrationTipCleanCmd struct{} + +func (*integrationTipCleanCmd) Help() string { + return text.Dedent(` + Removes tips whose upstack chain already contains another + configured tip. The higher tip's merge into the integration + branch captures the lower tip's content, so keeping both + costs an extra merge without changing the result. + + For each subsumed tip, the message reports which higher tip + subsumes it. A second run is a no-op once nothing remains + to prune. Existing tips with no upstack-tip relationship + are left alone. + `) +} + +func (cmd *integrationTipCleanCmd) Run( + ctx context.Context, + log *silog.Logger, + svc *spice.Service, + handler IntegrationHandler, +) error { + status, err := handler.Show(ctx) + if err != nil { + return err + } + + graph, err := svc.BranchGraph(ctx, nil) + if err != nil { + return fmt.Errorf("build branch graph: %w", err) + } + + subsumed := subsumedTips(graph, status.Tips) + if len(subsumed) == 0 { + log.Info("No subsumed tips to prune.") + return nil + } + + for _, s := range subsumed { + if err := handler.RemoveTip(ctx, s.tip); err != nil { + return fmt.Errorf("remove tip %q: %w", s.tip, err) + } + log.Infof("Removed %q (subsumed by %q).", s.tip, s.subsumer) + } + log.Infof("Pruned %d tip(s).", len(subsumed)) + return nil +} + +// subsumption records that tip is fully contained in the upstack of +// subsumer, both of which are configured integration tips. +type subsumption struct { + tip string + subsumer string +} + +// subsumedTips returns the set of tips that should be pruned because +// they are upstack-reachable from another configured tip. +// +// A tip T is subsumed when at least one branch above T (in the +// inclusive upstack of T, excluding T itself) is also a configured +// tip. The subsumer reported is the first such tip found while +// walking T's upstack; with cleanly-stacked branches this is stable. +// +// Tips whose branches are missing from the graph are ignored. +func subsumedTips( + graph *spice.BranchGraph, + tips []integration.TipStatus, +) []subsumption { + tipSet := make(map[string]struct{}, len(tips)) + for _, t := range tips { + if t.Missing { + continue + } + tipSet[t.Name] = struct{}{} + } + + var out []subsumption + for _, t := range tips { + if t.Missing { + continue + } + for above := range graph.Upstack(t.Name) { + if above == t.Name { + continue + } + if _, ok := tipSet[above]; ok { + out = append(out, subsumption{tip: t.Name, subsumer: above}) + break + } + } + } + return out +} diff --git a/testdata/help/gs.txt b/testdata/help/gs.txt index 9efb9c81a..784a34d55 100644 --- a/testdata/help/gs.txt +++ b/testdata/help/gs.txt @@ -31,7 +31,6 @@ Log Stack stack (s) submit (s) Submit a stack - stack (s) merge (m) Merge a stack stack (s) restack (r) Restack a stack stack (s) edit (e) Edit the order of branches in a stack stack (s) delete (d) Delete all branches in a stack @@ -59,7 +58,6 @@ Branch branch (b) restack (r) Restack a branch branch (b) onto (on) Move a branch onto another branch branch (b) diff (di) Show diff between a branch and its base - branch (b) merge (m) Merge a branch into trunk branch (b) submit (s) Submit a branch Commit @@ -86,6 +84,9 @@ Integration Remove a branch from the integration tip list integration (int) tip list (l,ls) List the configured integration tips + integration (int) tip clean (prune) + Remove tips whose upstack already contains + another tip Rebase rebase (rb) continue (c) Continue an interrupted operation diff --git a/testdata/help/integration_tip_clean.txt b/testdata/help/integration_tip_clean.txt new file mode 100644 index 000000000..4c0aa180d --- /dev/null +++ b/testdata/help/integration_tip_clean.txt @@ -0,0 +1,18 @@ +Usage: gs integration (int) tip clean (prune) + +Remove tips whose upstack already contains another tip + +Removes tips whose upstack chain already contains another configured tip. The +higher tip's merge into the integration branch captures the lower tip's content, +so keeping both costs an extra merge without changing the result. + +For each subsumed tip, the message reports which higher tip subsumes it. +A second run is a no-op once nothing remains to prune. Existing tips with no +upstack-tip relationship are left alone. + +Global Flags: + -h, --help Show help for the command + --version Print version information and quit + -v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE) + -C, --dir=DIR Change to DIR before doing anything + --[no-]prompt Whether to prompt for missing information diff --git a/testdata/script/integration_tip_clean.txt b/testdata/script/integration_tip_clean.txt new file mode 100644 index 000000000..61d6c88b5 --- /dev/null +++ b/testdata/script/integration_tip_clean.txt @@ -0,0 +1,68 @@ +# Verifies 'gs integration tip clean' removes tips whose upstack +# contains another configured tip. +# +# A tip whose upstack already contains a configured tip is fully +# subsumed: merging the higher tip into the integration already +# captures the lower tip's content. Keeping both inflates the +# rebuild for no benefit. + +as 'Test ' +at '2025-06-01T00:00:00Z' + +cd repo +git init +git commit --allow-empty -m 'Initial commit' +gs repo init + +# Linear stack: main -> low -> middle -> high +git add low.txt +gs branch create low -m 'Add low' +git add middle.txt +gs branch create middle -m 'Add middle' +git add high.txt +gs branch create high -m 'Add high' + +# Independent stack: main -> side +gs trunk +git add side.txt +gs branch create side -m 'Add side' + +# Configure every branch in the linear stack plus the independent +# 'side' branch as tips. low and middle are subsumed by high; side +# is independent and stays. +gs trunk +gs integration create preview --tip low --tip middle --tip high --tip side + +# Clean prunes the subsumed tips. The reported subsumer is the +# nearest configured tip up the stack, not the topmost: 'low' is +# subsumed by 'middle' (which is itself subsumed by 'high'). +gs integration tip clean +stderr 'Removed "low" \(subsumed by "middle"\)' +stderr 'Removed "middle" \(subsumed by "high"\)' +stderr 'Pruned 2 tip\(s\)' + +# Only the non-subsumed tips remain. +gs integration tip list +cmp stdout $WORK/golden/tips.txt + +# A second run is a no-op. +gs integration tip clean +stderr 'No subsumed tips to prune' + +# 'tip prune' is an alias. +gs integration tip add low +gs integration tip prune +stderr 'Removed "low" \(subsumed by "high"\)' +# Only 'high' remains in low's upstack chain, so it is the subsumer. + +-- repo/low.txt -- +low +-- repo/middle.txt -- +middle +-- repo/high.txt -- +high +-- repo/side.txt -- +side +-- golden/tips.txt -- +high +side From 2697d0b43a4acaadcaae1974124bc4894b0e6132 Mon Sep 17 00:00:00 2001 From: Edmund Kohlwey Date: Mon, 8 Jun 2026 07:11:08 -0400 Subject: [PATCH 2/4] integration-tip-clean: Regenerate derived files --- doc/includes/cli-reference.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/includes/cli-reference.md b/doc/includes/cli-reference.md index d4453bb70..d7a40cda9 100644 --- a/doc/includes/cli-reference.md +++ b/doc/includes/cli-reference.md @@ -245,9 +245,11 @@ Change Requests are created or updated for all branches in the current stack. When invoked from the configured integration branch, the -"current stack" is the union of every tip's full stack -(upstack and downstack), and the integration branch itself -is pushed afterward. +"current stack" is the union of each configured tip's +downstack (the tip and the branches below it), and the +integration branch itself is pushed afterward. Branches +above a tip are deliberately left alone: they are work in +progress that has not been promoted to a tip yet. Use --dry-run to print what would be submitted without submitting it. From 115c64724d272f35593ba19b58d56ad8f82afffb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:01:51 +0000 Subject: [PATCH 3/4] [autofix.ci] apply automated fixes --- doc/includes/cli-reference.md | 75 +++++++++++++++++++++++++++++++++-- testdata/help/gs.txt | 2 + 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/doc/includes/cli-reference.md b/doc/includes/cli-reference.md index d7a40cda9..d5dd4fcd7 100644 --- a/doc/includes/cli-reference.md +++ b/doc/includes/cli-reference.md @@ -293,6 +293,48 @@ only if there are multiple CRs in the stack. **Configuration**: [spice.submit.assignees](/cli/config.md#spicesubmitassignees), [spice.submit.draft](/cli/config.md#spicesubmitdraft), [spice.submit.labels](/cli/config.md#spicesubmitlabels), [spice.submit.labels.addWhen](/cli/config.md#spicesubmitlabelsaddwhen), [spice.submit.listTemplatesTimeout](/cli/config.md#spicesubmitlisttemplatestimeout), [spice.submit.navigationComment](/cli/config.md#spicesubmitnavigationcomment), [spice.submit.navigationComment.downstack](/cli/config.md#spicesubmitnavigationcommentdownstack), [spice.submit.navigationCommentStyle.marker](/cli/config.md#spicesubmitnavigationcommentstylemarker), [spice.submit.navigationCommentSync](/cli/config.md#spicesubmitnavigationcommentsync), [spice.submit.publish](/cli/config.md#spicesubmitpublish), [spice.submit.reviewers](/cli/config.md#spicesubmitreviewers), [spice.submit.reviewers.addWhen](/cli/config.md#spicesubmitreviewersaddwhen), [spice.submit.skipRestackCheck](/cli/config.md#spicesubmitskiprestackcheck), [spice.submit.template](/cli/config.md#spicesubmittemplate), [spice.submit.updateOnly](/cli/config.md#spicesubmitupdateonly), [spice.submit.web](/cli/config.md#spicesubmitweb) +### git-spice stack merge {#gs-stack-merge} + +``` +gs stack (s) merge (m) [flags] +``` + +:material-test-tube:{ title="Experimental" }[merge](/cli/experiments.md#merge) + +Merge a stack + +Merges the CRs for the current branch's stack into trunk. +Use --branch to merge a different branch's stack. + +The stack includes the selected branch, +its downstack branches down to trunk, +and every upstack branch. + +Already-merged branches are skipped automatically. +Branches must have an open Change Request to be merged. + +Before merging, the stack is checked for branches +whose base PR was already merged on the forge. +Use --no-branch-check to skip this validation. + +Before each merge, waits for CI checks to pass. +Use --build-timeout to configure the maximum wait +before failing if checks are not ready. + +By default, a branch failure skips that branch's upstack descendants, +but independent sibling branches continue. +Use --fail-fast to stop the queue after the first branch failure. + +**Flags** + +* `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. +* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. +* `--no-branch-check`: Skip stale base validation before merging. +* `--fail-fast`: Stop the merge queue after the first branch failure. +* `--branch=NAME`: Branch whose stack to merge + +**Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) + ### git-spice stack restack {#gs-stack-restack} ``` @@ -622,11 +664,11 @@ when you don't want to wait for the merge to propagate. **Flags** -* `--branch=NAME`: Branch to start merging from -* `--no-wait`: Skip polling for a single branch merge to propagate. -* `--no-branch-check`: Skip stale base validation before merging. * `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. * `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. +* `--no-wait`: Skip polling for a single branch merge to propagate. +* `--no-branch-check`: Skip stale base validation before merging. +* `--branch=NAME`: Branch to start merging from **Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) @@ -1082,6 +1124,33 @@ Use --branch to target a different branch. * `--branch=NAME`: Branch to diff +### git-spice branch merge {#gs-branch-merge} + +``` +gs branch (b) merge (m) [flags] +``` + +:material-test-tube:{ title="Experimental" }[merge](/cli/experiments.md#merge) + +Merge a branch into trunk + +Merges the CR for the current branch into trunk. +Use --branch to merge a different branch. + +The branch must be based directly on trunk. +To merge a stacked branch, use 'gs downstack merge'. + +Before merging, waits for CI checks to pass. +Use --build-timeout to configure the maximum wait. + +**Flags** + +* `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. +* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. +* `--branch=NAME`: Branch to merge + +**Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) + ### git-spice branch submit {#gs-branch-submit} ``` diff --git a/testdata/help/gs.txt b/testdata/help/gs.txt index 784a34d55..062531cf9 100644 --- a/testdata/help/gs.txt +++ b/testdata/help/gs.txt @@ -31,6 +31,7 @@ Log Stack stack (s) submit (s) Submit a stack + stack (s) merge (m) Merge a stack stack (s) restack (r) Restack a stack stack (s) edit (e) Edit the order of branches in a stack stack (s) delete (d) Delete all branches in a stack @@ -58,6 +59,7 @@ Branch branch (b) restack (r) Restack a branch branch (b) onto (on) Move a branch onto another branch branch (b) diff (di) Show diff between a branch and its base + branch (b) merge (m) Merge a branch into trunk branch (b) submit (s) Submit a branch Commit From 62dd94f0646c4c6c9ce12a04b186b47dd17695ad Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:38:18 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes --- doc/includes/cli-reference.md | 36 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/doc/includes/cli-reference.md b/doc/includes/cli-reference.md index d5dd4fcd7..32de51634 100644 --- a/doc/includes/cli-reference.md +++ b/doc/includes/cli-reference.md @@ -317,9 +317,11 @@ Before merging, the stack is checked for branches whose base PR was already merged on the forge. Use --no-branch-check to skip this validation. -Before each merge, waits for CI checks to pass. -Use --build-timeout to configure the maximum wait -before failing if checks are not ready. +Before each merge, waits for merge readiness: +the forge must observe the pushed head +and report that the CR is ready to merge. +Use --ready-timeout to configure the maximum wait +before failing if merge readiness is not reached. By default, a branch failure skips that branch's upstack descendants, but independent sibling branches continue. @@ -328,12 +330,12 @@ Use --fail-fast to stop the queue after the first branch failure. **Flags** * `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. -* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. +* `--ready-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.readyTimeout" }](/cli/config.md#spicemergereadytimeout)): Max time to wait for merge readiness before each merge. 0 means check once. * `--no-branch-check`: Skip stale base validation before merging. * `--fail-fast`: Stop the merge queue after the first branch failure. * `--branch=NAME`: Branch whose stack to merge -**Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) +**Configuration**: [spice.merge.method](/cli/config.md#spicemergemethod), [spice.merge.readyTimeout](/cli/config.md#spicemergereadytimeout) ### git-spice stack restack {#gs-stack-restack} @@ -631,7 +633,7 @@ This command acts as a local merge queue: it merges one Change Request, waits for that merge to finish, restacks and updates the next Change Request, -waits for its CI checks to pass, +waits for merge readiness on the updated Change Request, and then repeats the process. For a stack like this: @@ -649,13 +651,15 @@ Before merging, the downstack is checked for branches whose base PR was already merged on the forge. Use --no-branch-check to skip this validation. -Before each merge, waits for CI checks to pass. -Use --build-timeout to configure the maximum wait +Before each merge, waits for merge readiness: +the forge must observe the pushed head +and report that the CR is ready to merge. +Use --ready-timeout to configure the maximum wait (default: 30m, 0 means fail immediately if not ready). Between merges, the command waits for each merge to complete, restacks and updates the next PR, -waits for CI checks on the updated PR, +waits for merge readiness on the updated PR, and syncs merged branch cleanup. Use --no-wait for single branch merging @@ -665,12 +669,12 @@ when you don't want to wait for the merge to propagate. **Flags** * `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. -* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. +* `--ready-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.readyTimeout" }](/cli/config.md#spicemergereadytimeout)): Max time to wait for merge readiness before each merge. 0 means check once. * `--no-wait`: Skip polling for a single branch merge to propagate. * `--no-branch-check`: Skip stale base validation before merging. * `--branch=NAME`: Branch to start merging from -**Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) +**Configuration**: [spice.merge.method](/cli/config.md#spicemergemethod), [spice.merge.readyTimeout](/cli/config.md#spicemergereadytimeout) ### git-spice downstack edit {#gs-downstack-edit} @@ -1140,16 +1144,18 @@ Use --branch to merge a different branch. The branch must be based directly on trunk. To merge a stacked branch, use 'gs downstack merge'. -Before merging, waits for CI checks to pass. -Use --build-timeout to configure the maximum wait. +Before merging, waits for merge readiness: +the forge must observe the pushed head +and report that the CR is ready to merge. +Use --ready-timeout to configure the maximum wait. **Flags** * `--method=METHOD` ([:material-wrench:{ .middle title="spice.merge.method" }](/cli/config.md#spicemergemethod)): Preferred merge method. One of 'merge', 'squash', and 'rebase'. -* `--build-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.buildTimeout" }](/cli/config.md#spicemergebuildtimeout)): Max time to wait for CI checks before each merge. 0 means check once. +* `--ready-timeout=30m` ([:material-wrench:{ .middle title="spice.merge.readyTimeout" }](/cli/config.md#spicemergereadytimeout)): Max time to wait for merge readiness before each merge. 0 means check once. * `--branch=NAME`: Branch to merge -**Configuration**: [spice.merge.buildTimeout](/cli/config.md#spicemergebuildtimeout), [spice.merge.method](/cli/config.md#spicemergemethod) +**Configuration**: [spice.merge.method](/cli/config.md#spicemergemethod), [spice.merge.readyTimeout](/cli/config.md#spicemergereadytimeout) ### git-spice branch submit {#gs-branch-submit}