Skip to content

Add git stack get to pull a pull request's stack down - #65

Open
mvalipour wants to merge 3 commits into
DomBlack:mainfrom
mvalipour:add-git-stack-get
Open

mvalipour wants to merge 3 commits into
DomBlack:mainfrom
mvalipour:add-git-stack-get

Conversation

@mvalipour

@mvalipour mvalipour commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Reviewing someone else's stacked PRs meant creating each branch by hand and tracking them one at a time. git stack get takes a PR number, URL or branch name and brings its stack down locally.

How it works

  • If GitHub has the PR in a stack, gh stack checkout does the work and creates the missing branches.
  • If the PR is in no stack on GitHub (or the repo has no stacked PRs), get walks down the open PRs it is based on, down to the first base that has none, which becomes the trunk. It creates those branches from the remote and tracks them as a new stack, the way init does.
  • A target already tracked here is only brought up to date.
  • Existing branches are fast forwarded with sync's code. Nothing is rebased or pushed, and diverged branches are left alone with a warning.
  • Forks are refused, since their branches are not on our remote. A PR based on a branch already in another local stack is also refused.

Other changes

  • get alias, and stack_get MCP tool (with source, created, updated, behind in its result).
  • forge.PR port method, so a PR can be looked up by number, including forks. Fork is now set on forge.PullRequest.
  • forge.OpenPRFor port method, which finds a branch's open PR however old it is (the base-PR walk and branch-name targets use it, rather than the 500 most recent PRs).
  • A URL for another repository is refused, and a checkout that fails after branches moved is reported as partial (checkedOut: false) so undo still covers it.
  • Tracker.ImportStack port method, implemented by the ghstack adapter around gh stack checkout. Exit codes 2, 3 and 9 map to not-in-stack, a local ordering conflict, and stacks unavailable.
  • get is undoable, and the undo docs list it.
  • Shell completion offers open PRs from the cache (never refreshes it).
  • README, docs/architecture.md, docs/mcp.md, the skill and MCP instructions are updated.

Review notes

  • A branch that exists locally is never updated by gh stack checkout, so get fetches first and then runs sync's fast forward on the stack.
  • get on a lone PR records its stack the same way init does, so stack_submit can later push it.
  • The cmd/get_test.go, pkg/app/get_test.go, pkg/mcp/server_test.go and pkg/backend/ghstack tests cover the new paths, including undo and refusals.

Reviewing someone else's stacked PRs meant creating each branch by hand
and tracking them one at a time. get takes a PR number, URL or branch
name and brings its stack down: gh stack checkout does the work when
GitHub has the PR in a stack, and for a lone PR get walks down the open
PRs it is based on and tracks them as a new stack. Branches that already
exist are fast forwarded with sync's code, never rebased or pushed, and
forks are refused since their branches are not on our remote.

Also adds the get alias, the stack_get MCP tool, and the matching docs
and skill entries.
Copilot AI balanced review requested due to automatic review settings November 2, 2026 13:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unvalidated PR URLs can update an unrelated stack, and checkout failures can leave branch mutations without an undo entry.

6 open issues
What changed in this PR

Adds git stack get to bring a pull request and its stack into a local checkout, using the shared application layer for both CLI and MCP access.

Changes:

  • Imports GitHub stacks or builds a stack from a PR’s open base PRs, reusing sync’s fast-forward logic without rebasing or pushing.
  • Adds git get, the stack_get MCP tool, cached PR completion, and undo integration.
  • Extends forge and tracker ports, adds tests, and documents the new workflow.
File Description
README.md Documents get, its alias, limitations, and undo support.
pkg/​stack/​stacktest/​backend.go Adds configurable stack-import test support.
pkg/​stack/​ports.go Adds the ImportStack tracker method.
pkg/​mcp/​tools.go Registers and handles stack_get.
pkg/​mcp/​server.go Maps get CLI guidance to its MCP tool.
pkg/​mcp/​server_test.go Tests tool registration, get results, and fork refusal.
pkg/​mcp/​instructions.go Adds get to MCP client guidance.
pkg/​install/​skill/​SKILL.md Documents get in the installed skill.
pkg/​install/​install_test.go Includes get in expected installed aliases.
pkg/​gitconfig/​aliases.go Adds the git get alias.
pkg/​git/​branch.go Adds remote-tracking branch creation.
pkg/​forge/​github/​github.go Adds numbered PR lookup and fork metadata.
pkg/​forge/​github/​github_test.go Tests PR lookup and missing-PR errors.
pkg/​forge/​forge.go Extends the forge port with PR lookup and fork status.
pkg/​backend/​ghstack/​tracker.go Implements stack import through gh stack checkout.
pkg/​backend/​ghstack/​ghstack_test.go Tests import invocation and error mapping.
pkg/​app/​submit_test.go Updates the recording forge for PR lookup.
pkg/​app/​prs_test.go Adds shared fake PR lookup support.
pkg/​app/​mutate_test.go Updates the fake backend for stack import.
pkg/​app/​get.go Implements target resolution, import, refresh, checkout, and undo integration.
pkg/​app/​get_test.go Covers import paths, repeated use, refusals, and undo.
docs/​mcp.md Documents stack_get and its undo support.
docs/​architecture.md Explains import delegation and PR-chain fallback.
cmd/​root.go Registers the get command.
cmd/​get.go Adds CLI wiring, help, and result rendering.
cmd/​get_test.go Tests command output, checkout, and argument validation.
cmd/​complete.go Adds cache-only open-PR completion.

Review effort: Balanced

Comment thread pkg/app/get.go Resolved
Comment thread pkg/app/get.go Resolved
Comment thread pkg/app/get.go Resolved
Comment thread pkg/app/get.go Resolved
Comment thread pkg/app/get.go Resolved
Comment thread pkg/app/get.go Resolved
@DomBlack DomBlack added the enhancement New feature or request label Nov 2, 2026
- Refuse a pull request URL for another repository instead of reading
  its number as one of ours.
- Report a failed checkout after branches moved as a partial result, so
  undo records it; the result's checkedOut says whether it happened.
- Look an untracked pull request number up on the forge and check its
  branch against the graph, so getting a lone PR again finds it.
- Work out needsRestack after the fast forwards, for every source.
- Find base pull requests by branch with Forge.OpenPRFor rather than in
  the 500 most recent, which also runs the lookup on the step's context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 2, 2026 14:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment thread pkg/app/get.go Resolved
Comment thread pkg/backend/ghstack/tracker.go Resolved
Comment thread pkg/app/get.go Resolved
Comment thread pkg/app/get.go Resolved
- When gh stack checkout fails after saving the imported stack, carry on
  with the target from that stack so the failed checkout is a partial,
  undoable result.
- Refuse a closed, merged or fork pull request before gh stack sees it.
- Check every branch is on the remote before creating any for a lone pull
  request, and delete the ones created if tracking them fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 4, 2026 17:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Unresolved issues allow invalid imports, reject valid targets, leave stale stack boundaries, and cause partial mutations that cannot be fully rolled back or undone.

No open issues 🎉

7 previously missed issues

In code that hasn't changed since last review

Medium severity Undo does not restore the original detached HEAD

pkg/​app/​get.go:123

Starting get from detached HEAD creates a stack that undo cannot fully restore: planWorktreeMoves skips the saved detached checkout. Undo removes the metadata but leaves the new branch checked out instead of returning to the original commit. Restore detached HEAD during undo, or reject this starting state before get mutates anything.

Medium severity Sync fails when the default trunk exists only on the remote

pkg/​app/​get.go:145

With no tracked stacks, gatherSync requires an existing local default branch, even when origin/HEAD names a remote-only trunk. A clone containing only a feature branch therefore fails before looking up the PR, although the import could create the missing trunk. Discover the fetch remote without requiring a local trunk, then obtain the trunk from the imported stack or PR chain.

Medium severity Post-import failures leave changes without undo records

pkg/​app/​get.go:200

Cancelling the “Checking ” step after import leaves the newly recorded stack and any completed fast-forwards in place, but this ordinary error prevents undo from recording them. Handle failures from the first mutation onward as rollback or undoable partial results, including the later getNeedsRestack failure. Populate the result before these steps so CLI and MCP callers retain what changed.

Medium severity Fast-forwarding leaves child base metadata stale

pkg/​app/​get.go:209

If local a is at A1 while remote a and child b contain A2, initialization records b.Base = A1, and fast-forwarding a leaves that boundary stale despite reporting no restack needed. Amending A2 with modify then makes the planner replay A2 as part of b, which can cause a spurious conflict. Reconcile recorded bases with final parent tips where the child contains them, preserving boundaries for diverged branches and branches sitting exactly on their parent.

Medium severity Untracked branch targets bypass PR eligibility checks

pkg/​app/​get.go:282

Untracked branch-name targets bypass the pre-import eligibility check. If a merged branch still exists in a GitHub stack with an open PR above it, get <branch> imports and checks it out, although getting the same PR by number is refused. Resolve the branch's open PR before importing and reject targets without one.

Medium severity Failed imports leave newly created branches untracked

pkg/​app/​get.go:343

gh stack checkout can create branches before saving metadata: importing [a, topic/b] with an existing local topic branch creates a, then fails on the ref-name conflict. No saved stack is found here, so a remains untracked and the ordinary error leaves no undo entry. Roll back branches created by the failed import; marking it partial alone is insufficient because undo excludes untracked refs.

Medium severity Fork PRs crowd out native matches before the result limit

pkg/​forge/​github/​github.go:223

The 20-result limit is applied before forks are filtered out, so 20 newer fork PRs named patch-1 can hide an older same-repository PR with that head name. This rejects a valid branch target or ends the base-PR walk early, incorrectly treating that branch as trunk. Exclude forks server-side before limiting, or paginate until a native match is found or all results are exhausted.

4 resolved since last review

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants