Skip to content

Merge Stacked PRs with merge command - #307

Open
skarim wants to merge 10 commits into
skarim/docs-minor-updatesfrom
skarim/merge-cmd
Open

Merge Stacked PRs with merge command#307
skarim wants to merge 10 commits into
skarim/docs-minor-updatesfrom
skarim/merge-cmd

Conversation

@skarim

@skarim skarim commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

This adds a new gh stack merge command built on GitHub's new async merge API, which can land multiple stacked PRs atomically. It merges some or all of a stack in one atomic operation: every PR up to and including the one you choose is merged into the base branch, and if any PR can't be merged, none are.

What's new

  • Three ways to target a merge. No argument merges the current active local stack; a stack number merges a stack you don't have checked out (a purely remote operation); a PR number merges directly up to that PR.
  • Interactive wizard. In a terminal, a three-step wizard lets you choose how far up the stack to merge (a bottom-anchored checkbox list, so everything below your selection is always included), pick the merge method (only the methods the repo allows, defaulting to your last-used one), and confirm — then a live view polls the merge to completion.
  • Non-interactive / agent-friendly. With --yes or no TTY, the whole stack (or everything up to the given PR) merges without prompting, using your last-used merge method unless one is given via --merge / --squash / --rebase / --merge-method.
  • Light pre-checks only. PRs are checked for being open and non-draft; GitHub evaluates branch protection and repository rules at merge time and reports the failure reason back. Because the merge is atomic, a failure lands nothing.
  • Merge queues are handled automatically. The merge is submitted with merge_action: "default", so GitHub either merges the stack directly or, when the base branch uses a merge queue, adds it to the queue (reported back as an enqueued result). Bypassing merge requirements with admin privileges is not supported for stacks.

Changes

The command

  • cmd/merge.go — the new gh stack merge command: resolves the target stack (current branch / stack number / PR number), computes the mergeable candidates (open, non-draft, contiguous from the bottom), and runs either the interactive wizard or the headless submit-and-poll path. Registered in cmd/root.go.

GitHub client

  • internal/github/merge_async.go — an async stack-merge client over the REST API: MergeStackAsync (submit) and GetAsyncMergeResult (poll) on go-gh's REST client, returning a status enum (pending / merged / enqueued / failed); plus RepoMergeConfig (allowed methods and last-used default, via GraphQL) and PRTitles (batch PR titles for the picker). Added to ClientOps and MockClient.

Wizard TUI

  • internal/tui/mergeview/ (new package) — the Bubble Tea wizard: a stepper banner, the bottom-anchored PR picker with a scrolling viewport, the allowed-method picker, the confirm step, and the live progress view.

Docs

  • README.md, docs/ CLI reference and workflows guide, skills/gh-stack/SKILL.md, and AGENTS.md — document the command, its targeting and merge-method behavior, and how merges route to a direct merge or the merge queue.

Testing

  • Unit tests for the command paths (cmd/merge_test.go), the async-merge client including status decoding and error mapping (internal/github/merge_async_test.go), and the wizard model (internal/tui/mergeview/model_test.go).
  • go test -race -count=1 ./... and go vet ./... are clean, and the changed files pass gofmt.

Stack created with GitHub Stacks CLIGive Feedback 💬

@skarim
skarim force-pushed the skarim/merge-cmd branch 2 times, most recently from a14ba2a to c8efa62 Compare July 25, 2026 17:06
@skarim
skarim force-pushed the skarim/merge-cmd branch 2 times, most recently from d69a1b4 to 0ff36e1 Compare July 26, 2026 00:33
Base automatically changed from skarim/docs-content-updates to main July 26, 2026 15:26
@skarim
skarim force-pushed the skarim/merge-cmd branch from 0ff36e1 to f0ec7d5 Compare July 26, 2026 15:29
@skarim
skarim marked this pull request as ready for review July 28, 2026 21:08
Copilot AI review requested due to automatic review settings July 28, 2026 21:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds gh stack merge for atomic stacked-PR merges through GitHub’s async merge API.

Changes:

  • Adds local, stack-number, and PR-number merge targeting.
  • Adds interactive and headless merge workflows.
  • Extends GitHub clients, tests, and documentation.
Show a summary per file
File Description
cmd/merge.go Implements merge command behavior.
cmd/merge_test.go Tests merge command paths.
cmd/root.go Registers the command.
cmd/root_test.go Tests command registration.
internal/github/merge_async.go Adds async merge API support.
internal/github/merge_async_test.go Tests async merge handling.
internal/github/client_interface.go Extends the client interface.
internal/github/mock_client.go Extends the mock client.
internal/tui/mergeview/model.go Implements wizard state and polling.
internal/tui/mergeview/model_test.go Tests wizard behavior.
internal/tui/mergeview/types.go Defines wizard types.
internal/tui/mergeview/view.go Renders the wizard.
README.md Documents the command.
docs/src/content/docs/reference/cli.md Adds CLI reference material.
docs/src/content/docs/guides/workflows.md Adds merge workflow guidance.
skills/gh-stack/SKILL.md Adds agent-facing merge guidance.
AGENTS.md Updates architecture documentation.
.github/copilot-instructions.md Updates Copilot repository guidance.

Review details

Comments suppressed due to low confidence (1)

internal/tui/mergeview/model.go:292

  • A polling transport/API error does not mean the server-side merge failed—the request may still complete—but this marks it failed and the command reports merge failed. That can make users retry an in-flight atomic merge or believe nothing landed. Preserve an unknown/watch-stopped outcome and report that only status polling stopped.
func (m Model) handlePollDone(msg pollDoneMsg) (tea.Model, tea.Cmd) {
	if msg.err != nil {
		m.err = msg.err
		m.failed = true
		m.message = msg.err.Error()
		return m.finish()
  • Files reviewed: 18/18 changed files
  • Comments generated: 13
  • Review effort level: Medium

Comment thread cmd/merge.go
Comment thread internal/tui/mergeview/view.go Outdated
Comment thread internal/github/merge_async.go Outdated
Comment thread internal/tui/mergeview/model.go
Comment thread cmd/merge.go
Comment thread docs/src/content/docs/reference/cli.md
Comment thread docs/src/content/docs/guides/workflows.md
Comment thread skills/gh-stack/SKILL.md
Comment thread cmd/merge.go
Comment thread .github/copilot-instructions.md Outdated
@skarim
skarim changed the base branch from main to skarim/docs-minor-updates July 28, 2026 21:33
@skarim
skarim force-pushed the skarim/merge-cmd branch from 5847fdd to a4ae722 Compare July 28, 2026 21:34
@skarim
skarim force-pushed the skarim/merge-cmd branch from a4ae722 to a6ff849 Compare July 28, 2026 22:35
@skarim
skarim force-pushed the skarim/merge-cmd branch from a6ff849 to 8773908 Compare July 28, 2026 22:47
skarim added 7 commits July 28, 2026 22:16
Follow-up polish for `gh stack merge` (the command itself landed in the
previous commit). These changes refine the interactive wizard, enrich the
PR picker, and replace the merge client's bespoke HTTP handling with the
standard go-gh REST client.

Wizard and stepper:
- Redesign the top stepper as a segmented bar: completed steps are green,
  the active step is the brightest, and upcoming steps are dimmed. Steps
  are separated by a Powerline arrow that blends into the shading, with a
  graceful fallback to abutting segments on terminals that lack the glyph
  (e.g. Apple Terminal). Set GH_STACK_POWERLINE=1/0 to override detection.
- Show the stack number in the header ("Merge stack #123").
- Hide the header and stepper once the merge is submitted so the live
  progress view stands on its own.

PR picker:
- Render each pull request on two lines: the title (white/black, a touch
  bolder when selected) above its "#number • branch" (gray, fainter when
  deselected). Titles are fetched in one batched GraphQL query (PRTitles)
  and fall back to the branch name.
- Scroll long stacks in a fixed 10-item window with persistent "N more"
  indicators, so the list no longer jumps as those hints appear and
  disappear. Add shift+up / shift+down to jump to the top or bottom.

Progress and outcome:
- Always render a status line ("Submitting merge request...") so it does
  not pop in later and shift the view, and normalize messages to end in an
  ellipsis.
- Print the final result from the command layer rather than the TUI: a
  success line that includes the merge commit SHA
  ("Merged #1, #2 into main (abc1234)"), an atomic-rollback note on
  failure, a distinct message when the user stops watching an in-flight
  merge, and "Cancelled operation, nothing merged" on cancel.
- Clamp every rendered line to the terminal width so resizing no longer
  leaves duplicated header lines behind, and make truncation ANSI-aware.

Async-merge client:
- Use the go-gh REST client (c.rest.Put / c.rest.Get) for both the submit
  and poll endpoints, removing the bespoke http.Client, base-URL helper,
  and manual response decoding. The REST client discards non-2xx bodies,
  but that only costs the rare 400 message and 409 UUID: real merge
  failures still surface through the 200 poll body, and the in-range PRs
  are validated open, non-draft, and non-merged before submitting.
- Add classifyAsyncMergeError to map status codes to clear errors (404
  unavailable, 409 already exists, 400 no longer mergeable) and drop the
  now-unused AsyncMergeResult.StatusCode field. Rework the client tests to
  drive the REST client through a stub http.RoundTripper.
@skarim
skarim force-pushed the skarim/merge-cmd branch from 8773908 to 7180c9c Compare July 29, 2026 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants