Files
github__gh-stack/.github/copilot-instructions.md
Sameen Karim 68ce60c760 Merge Stacked PRs with merge command (#307)
* 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
2026-07-29 13:32:07 -04:00

2.4 KiB

gh-stack: Copilot Instructions

A Go CLI extension (gh stack) for managing stacked branches and pull requests. Uses Cobra for commands, bubbletea/lipgloss for TUI, and stretchr/testify for tests.

Build and validate

go mod download                  # install deps
go build -o gh-stack .           # build
go vet ./...                     # static analysis. Always run before tests.
go test -race -count=1 ./...     # tests with race detection

No Makefile, no code generation, no external linter config. Standard Go toolchain only.

Project layout

  • cmd/: One Cobra command per file. Each exports <Name>Cmd(cfg *config.Config) with logic in run<Name>().
  • internal/git/: Ops interface (52 methods) wrapping git CLI. MockOps for tests. Package-level functions delegate to swappable ops variable.
  • internal/github/: ClientOps interface (18 methods) for GitHub API. MockClient for tests. Stack operations use the public Stacks REST API (/repos/{owner}/{repo}/stacks); merges use the async merge API (/repos/{owner}/{repo}/pulls/{n}/merge-async) with an explicit merge_action (direct_merge or merge_queue) chosen from the base branch's merge-queue detection. merge_action is optional — omitting it (or sending default) lets the server auto-route (merge queue if one is configured, else direct merge) — but the CLI sends it explicitly so a wrong detection fails loudly instead of silently merging directly.
  • internal/config/: Config struct passed to all commands. Holds I/O, colors, and test hooks (SelectFn, ConfirmFn, InputFn, GitHubClientOverride).
  • internal/stack/: Stack file (.git/gh-stack, JSON) management with file locking.
  • internal/tui/: bubbletea views (stackview, modifyview).

Coding conventions

  • Return typed ExitError sentinels (codes 1-10 in cmd/utils.go) from RunE. Never call os.Exit() directly.
  • Check errors with var exitErr *ExitError; errors.As(err, &exitErr).
  • Table-driven tests with t.Run() subtests.
  • Use config.NewTestConfig() for test configs with captured I/O.
  • Mock git: restore := git.SetOps(&git.MockOps{...}); defer restore(). Always defer restore.
  • Mock GitHub: cfg.GitHubClientOverride = &github.MockClient{...}.
  • Mock prompts: set cfg.SelectFn, cfg.ConfirmFn, or cfg.InputFn.
  • Load stack files with stack.Load(dir) after writing to get correct checksums.

For full architecture details, see AGENTS.md in the repository root.