Skip to content

Stacks CLI prototype with core operations - #1

Merged
skarim merged 78 commits into
mainfrom
skarim/gh-stack-prototype
Mar 25, 2026
Merged

Stacks CLI prototype with core operations#1
skarim merged 78 commits into
mainfrom
skarim/gh-stack-prototype

Conversation

@skarim

@skarim skarim commented Feb 18, 2026

Copy link
Copy Markdown
Collaborator

gh stack CLI extension

A GitHub CLI extension for managing stacked branches and pull requests. Breaks large changes into small, reviewable layers with automated branch management, cascading rebases, and PR creation.


Local stack management

  • init [branches...] — Initialize a new stack. Supports interactive (prompts for branch names), explicit (creates branches from args), and adopt (--adopt) modes. Auto-detects trunk from origin/HEAD. Enables git rerere for conflict reuse. Prevents initializing inside an existing stack. Persists state to .git/gh-stack. Supports --prefix to set a branch name prefix and --numbered (with --prefix) for auto-incrementing branch names (prefix/01, prefix/02, …). In interactive mode, prompts for an optional prefix.
  • add [branch] — Add a new branch on top of the current stack. Must be run from the topmost branch. Supports staging and committing as part of the add flow: -A stages all changes (including untracked), -u stages tracked files only, and -m creates a commit with the given message. When -m is provided without an explicit branch name, the branch name is auto-generated (numbered format if the stack uses --numbered, otherwise date+slug format). On a branch with no commits yet, add -Am commits directly on it rather than creating a new one.
  • unstack [branch] — Remove a stack from local tracking and optionally from GitHub. With --local, only removes local metadata.

Viewing and navigation

  • view — Display the stack structure with branch status, PR links, and last commit info. Status indicators: merged, needs rebase, open PR. Pipes through pager (GIT_PAGER / PAGER / less -R). Supports --short (compact branch list) and --json (machine-readable output).
  • checkout [<pr-or-branch>] — Check out a locally tracked stack from a PR number or branch name. Interactive mode shows a menu of all locally available stacks. Currently limited to stacks created locally via gh stack init; server-side stack discovery is not yet implemented.
  • up [n], down [n], top, bottom — Navigate between branches in the stack. Clamps to bounds — moving past the top or bottom is a no-op with a message.

Rebasing

  • rebase [branch] — Cascading rebase of all branches in the stack. Fetches from origin first. Detects squash-merged PRs and uses git rebase --onto to avoid duplicate-patch conflicts. Scoping: --downstack (trunk to current), --upstack (current to top). Conflict handling: pauses and prints conflicted files with line numbers; resume with --continue, undo with --abort (restores all branches to pre-rebase SHAs). Leverages git rerere to auto-resolve previously seen conflicts. Supports --remote to specify which remote to fetch from.

Remote operations & PR workflow

  • push — Push all branches and create/update pull requests. Uses --force-with-lease by default for safety after rebases. Creates PRs with branch-chaining (each PR targets the branch below it). Prompts for PR titles on new PRs (press Enter for branch name default, or use --auto to skip prompting). Supports --draft, --skip-prs (push branches without creating/updating PRs), and --remote.
  • sync — All-in-one fetch → fast-forward trunk → cascade rebase → push → sync PR state. Performs each step safely: fast-forward is skipped if trunk has diverged, rebase only runs if trunk moved, push uses --force-with-lease after rebase. On conflict, aborts and restores all branches, directing the user to gh stack rebase for manual resolution. Supports --remote.

Planned (not yet implemented)

  • merge <pr> — Merge a stack of PRs. Currently prints a notice that it is not yet implemented.

Other

  • feedback [title] — Opens the gh-stack GitHub Discussions page to submit feedback.

Abbreviated workflow

With --prefix + --numbered on init and -Am on add, the typical workflow collapses to minimal keystrokes — no need to name branches, run git add, or run git commit separately:

gh stack init -p feat --numbered   # creates feat/01
# ... write code ...
gh stack add -Am "Auth middleware"  # commits on feat/01 (no commits yet)
# ... write code ...
gh stack add -Am "API routes"      # creates feat/02, commits there
gh stack push                      # pushes all, creates PRs

Internals

  • Persistence — Stack state stored as JSON in .git/gh-stack (schema version 1). Tracks repository context (host:owner/repo), trunk, branches with merge-base SHAs, and PR metadata (number, GraphQL ID, URL, merged status). Rebase state stored in .git/gh-stack-rebase-state.
  • Git operations — Thin wrapper over cli/cli/v2/git.Client. Custom support for --force-with-lease push, three-argument rebase --onto, rerere auto-continue, conflict marker detection, and fast-forward merge.
  • GitHub API — GraphQL for PR queries/mutations (FindPRForBranch, CreatePR, UpdatePRBase). REST fallback. Auth via GitHub CLI defaults.
  • Config — Colored terminal output (auto-disabled without TTY). Structured output methods (Successf, Errorf, Warningf, Infof).
  • Exit codes — Differentiated codes: 0 success, 1 generic, 2 not-in-stack, 3 conflict, 4 API failure, 5 invalid args, 6 disambiguation, 7 rebase in progress.
  • Squash-merge handling — Detects merged PRs via synced PR state. Triggers --onto mode for subsequent rebases to replay only unique commits.

Copilot AI review requested due to automatic review settings February 18, 2026 10:01
@skarim skarim changed the title Stack CLI prototype with local stack management and API placeholders Stacks CLI prototype with core operations Feb 18, 2026

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

This PR introduces an initial gh stack CLI prototype, including local stack state persistence in .git/gh-stack, basic stack operations (init/add/view/update/navigation), and GitHub PR create/update plumbing with placeholders for stack-aware APIs.

Changes:

  • Adds local stack state model + persistence and a git helper layer used by commands.
  • Implements core CLI commands (init, add, view, update, push, navigation) plus placeholder commands (checkout, merge, hidden stubs).
  • Wires the extension entrypoint to the Cobra command tree and adds dependencies for CLI UX and GitHub API access.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
main.go Switches entrypoint to execute the Cobra CLI.
go.mod Updates module path and adds required CLI/GitHub dependencies.
go.sum Adds checksums for new dependencies.
cmd/root.go Defines the stack root command and registers subcommands.
cmd/init.go Initializes/adopts a stack and persists state in .git/gh-stack.
cmd/add.go Adds a new branch on top of the current stack.
cmd/navigate.go Adds stack navigation commands (up/down/top/bottom).
cmd/view.go Displays stack state (short/full) and optionally opens PRs in browser.
cmd/update.go Implements fetch + cascading rebase with conflict state persistence.
cmd/push.go Pushes branches and creates/updates PRs (stack-linking is a stub).
cmd/checkout.go Placeholder for future stack checkout workflow.
cmd/merge.go Placeholder for future stack merge workflow.
cmd/placeholder.go Hidden placeholder commands for planned features.
internal/stack/stack.go Stack data structures + JSON load/save in .git/gh-stack.
internal/git/git.go Git helper wrapper around cli/v2/git plus custom helpers.
internal/config/config.go Shared config for terminal IO and colorized output.
internal/github/github.go GitHub API client wrapper (GraphQL PR find/create/update).
Comments suppressed due to low confidence (2)

cmd/update.go:327

  • abortUpdate ignores errors from checkout/reset for each branch, which can leave the repo partially restored while still reporting success. Consider handling errors (at least collecting/reporting them and returning a non-zero error) so users know when a restore did not fully succeed.
	for branch, sha := range state.OriginalRefs {
		_ = git.CheckoutBranch(branch)
		_ = git.ResetHard(sha)
	}

cmd/update.go:339

  • saveRebaseState discards JSON marshal/write errors. If the state file can’t be written, --continue/--abort won’t work but the user won’t know why. Return an error from saveRebaseState and surface it where it’s called (especially on the first conflict).
func saveRebaseState(gitDir string, state *rebaseState) {
	data, _ := json.MarshalIndent(state, "", "  ")
	_ = os.WriteFile(filepath.Join(gitDir, rebaseStateFile), data, 0644)
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/git/git.go Outdated
Comment thread cmd/navigate.go Outdated
Comment thread cmd/push.go Outdated
Comment thread cmd/init.go Outdated
Comment thread cmd/rebase.go Outdated
Comment thread cmd/view.go Outdated
Comment thread internal/github/github.go
Comment thread cmd/view.go

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

This PR turns gh-stack into a functional GitHub CLI extension prototype by introducing the core command set, local stack persistence, and supporting git/GitHub API wrappers.

Changes:

  • Replace the previous main.go prototype with a Cobra-based gh stack command tree (init/add/view/rebase/push/sync/unstack + placeholders).
  • Add local stack persistence (.git/gh-stack) with a JSON schema and associated stack model/helpers.
  • Introduce internal wrappers for git operations and GitHub API calls to support PR discovery/creation and rebasing workflows.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
main.go Switches entrypoint to execute the Cobra CLI.
go.mod Updates module path and adds dependencies for CLI + TUI + GitHub operations.
go.sum Adds/upgrades dependency checksums consistent with new imports.
README.md Documents installation, concepts, and the new command set/workflows.
internal/stack/stack.go Implements stack persistence model + load/save helpers for .git/gh-stack.
internal/stack/schema.json Defines JSON schema for the stack state file.
internal/github/github.go Adds GitHub client wrapper (GraphQL/REST) for PR operations.
internal/git/git.go Adds git wrapper utilities for branching, pushing, rebasing, and conflict detection.
internal/config/config.go Adds shared config for IO, terminal detection, and styled output.
cmd/root.go Adds root Cobra command and wires up subcommands.
cmd/init.go Implements local stack initialization (interactive/explicit/adopt).
cmd/add.go Implements adding a new branch at the top of the current stack.
cmd/view.go Implements stack visualization (short/full) + pager + open-in-browser.
cmd/rebase.go Implements cascading rebase with conflict state persistence and resume/abort.
cmd/push.go Implements pushing stack branches and creating/updating chained PRs.
cmd/sync.go Implements fetch → trunk FF → cascade rebase → push → PR sync workflow.
cmd/unstack.go Implements removing a stack from local tracking and (placeholder) GitHub deletion.
cmd/navigate.go Implements stack navigation commands (up/down/top/bottom).
cmd/utils.go Adds stack disambiguation + PR metadata syncing helpers.
cmd/placeholder.go Adds hidden placeholder commands for planned features.
cmd/checkout.go Adds placeholder for stack checkout workflow.
cmd/merge.go Adds placeholder for stack merge workflow.
cmd/feedback.go Adds command to open a Discussions feedback form.
Comments suppressed due to low confidence (2)

cmd/utils.go:33

  • This message includes a trailing newline, but Warningf already prints one. This will produce an extra blank line in the output; remove the \n from the format string.
	cfg.Warningf("Branch %q is the trunk of multiple stacks\n", branch)

cmd/view.go:306

  • runView already synced PR state before invoking viewWeb, but viewWeb still performs a GitHub API lookup for every branch (FindPRForBranch). This is redundant and can significantly slow down --web for large stacks. Prefer opening br.PullRequest.URL when present, and only querying when it’s missing.
	opened := 0
	for _, br := range s.Branches {
		pr, err := client.FindPRForBranch(br.Branch)
		if err != nil || pr == nil {
			continue
		}
		url := fmt.Sprintf("https://github.com/%s/%s/pull/%d", repo.Owner, repo.Name, pr.Number)
		if err := b.Browse(url); err != nil {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread cmd/utils.go Outdated
Comment thread cmd/add.go Outdated
Comment thread cmd/feedback.go Outdated
Comment thread cmd/rebase.go Outdated
Comment thread cmd/view.go
Comment thread cmd/sync.go Outdated
Comment thread cmd/root.go
Comment thread cmd/utils.go
Comment thread cmd/merge.go Outdated
Comment thread internal/stack/stack.go

Copilot AI commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Warning

This is an internal experiment to assess Copilot's ability to auto-approve PRs. Please 👍 this comment if the assessment below is correct and 👎 if not. Feedback in #f-ccr-auto-approve is appreciated!

Copilot thinks this PR is not ready to merge — see review comments for details.

@skarim
skarim requested a review from georgebrock March 23, 2026 08:50
@skarim
skarim merged commit de20682 into main Mar 25, 2026
7 checks passed
@skarim
skarim deleted the skarim/gh-stack-prototype branch March 25, 2026 21:57
skarim added a commit that referenced this pull request Jul 23, 2026
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 added a commit that referenced this pull request Jul 25, 2026
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 added a commit that referenced this pull request Jul 25, 2026
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 added a commit that referenced this pull request Jul 26, 2026
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 added a commit that referenced this pull request Jul 26, 2026
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 added a commit that referenced this pull request Jul 28, 2026
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 added a commit that referenced this pull request Jul 29, 2026
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 added a commit that referenced this pull request Jul 29, 2026
* merge cmd

* Refine the merge TUI and simplify the async-merge client

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.

* warn merge queue unsupported

* update for new status field from api

* merge cmd docs

* more helpful error msgs

* update to support merge queue

* addressing review comments

* hide merge method step for merge queue

* set merge action explicitly

* address review comments to clarify docs on merge/api behavior
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.

4 participants