Skip to content

link command for api-only operations#50

Open
skarim wants to merge 5 commits intoskarim/ignore-merged-prsfrom
skarim/simple-api-wrapper
Open

link command for api-only operations#50
skarim wants to merge 5 commits intoskarim/ignore-merged-prsfrom
skarim/simple-api-wrapper

Conversation

@skarim
Copy link
Copy Markdown
Collaborator

@skarim skarim commented Apr 17, 2026

gh stack link — stacks API-wrapper for external tool users

Users who manage branches with external tools (jj, Sapling, ghstack, git-town) currently need to run init --adopt + submit to create GitHub stacks — but that creates local tracking state they don't want. This adds a lightweight gh stack link command that works entirely via the GitHub API without creating/modifying any local state.

Usage:

gh stack link [--base main] [--draft] [--remote origin] <branch-or-pr> [...]

Arguments can be branch names, PR numbers, or a mix. Branches are pushed automatically, PRs are created for branches that don't have them, and the PRs are linked as a stack on GitHub.

How it works:

  1. Push — pushes branch args to the remote (skipped for PR-number-only invocations)
  2. Find existing PRs — resolves each arg to a PR without creating anything yet. Numeric args try FindPRByNumber first; if not found, fall through to branch lookup via FindPRForBranch
  3. Pre-validate — calls ListStacks and checks that the operation won't remove existing PRs from a stack (additive-only safety). Fails early before creating any new PRs
  4. Create missing PRs — creates PRs for branches that don't have one, with correct base chaining (first branch uses --base, subsequent branches chain off the previous)
  5. Fix base branches — corrects BaseRefName on existing PRs whose base doesn't match the expected chain (skips newly created PRs)
  6. Upsert stack — creates a new stack or updates an existing one

Safety features:

  • Additive-only updates — if the input would remove PRs from an existing stack, the command errors with a clear message showing the current stack composition
  • Pre-validation before PR creation — stack conflicts are caught before creating new PRs, preventing orphaned PRs
  • Fatal PR lookup errorsFindPRByNumber API errors are treated as fatal (not silently falling through to branch-name lookup)
  • Single ListStacks call — pre-validation and upsert share the same API response

Flags:

Flag Description
--base <branch> Base branch for the bottom of the stack (default: main)
--draft Create new PRs as drafts
--remote <name> Remote to push to (defaults to auto-detected remote)

Copilot AI review requested due to automatic review settings April 17, 2026 07:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new gh stack link command intended for “API-first” stacked PR operations—linking existing PRs and/or branches into a GitHub stack without creating gh-stack local tracking state—along with full documentation and test coverage.

Changes:

  • Introduces gh stack link CLI command and wires it into the root command.
  • Implements link flow: push branch args, resolve/create PRs, fix base-branch chaining, then create/update the remote stack (additive-only).
  • Adds extensive unit tests plus updates README, CLI reference docs, FAQ, and the gh-stack skill guide.
Show a summary per file
File Description
skills/gh-stack/SKILL.md Updates agent rules and adds a new section documenting gh stack link.
docs/src/content/docs/reference/cli.md Adds CLI reference documentation for gh stack link.
docs/src/content/docs/faq.md Updates FAQ to recommend gh stack link for external stacking tools.
cmd/root.go Registers LinkCmd in the CLI.
cmd/link.go Implements the link command (push/resolve/create/fix-base/upsert-stack).
cmd/link_test.go Adds comprehensive tests for PR-number, branch-name, and mixed workflows.
README.md Documents gh stack link usage and flags.

Copilot's findings

Tip

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

  • Files reviewed: 7/7 changed files
  • Comments generated: 5

Comment thread docs/src/content/docs/reference/cli.md Outdated
Comment thread README.md Outdated
Comment thread skills/gh-stack/SKILL.md Outdated
Comment thread cmd/link.go
Comment thread cmd/link.go Outdated
@skarim skarim force-pushed the skarim/simple-api-wrapper branch 2 times, most recently from 32de12d to ced6b5e Compare April 17, 2026 12:30
@skarim skarim force-pushed the skarim/ignore-merged-prs branch 2 times, most recently from 260a40b to d4c7e0c Compare April 17, 2026 20:50
@skarim skarim force-pushed the skarim/simple-api-wrapper branch from ced6b5e to a2c7a18 Compare April 17, 2026 20:50
@skarim skarim force-pushed the skarim/ignore-merged-prs branch from d4c7e0c to 08df626 Compare April 17, 2026 21:50
@skarim skarim force-pushed the skarim/simple-api-wrapper branch from a2c7a18 to 0386acd Compare April 17, 2026 21:50
Comment thread cmd/link.go Outdated
Long: `Create or update a stack on GitHub from branch names or PR numbers.

This command does not rely on gh-stack local tracking state. It is
designed for users who manage branches with external tools (e.g. jj)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe provide other examples here?

Suggested change
designed for users who manage branches with external tools (e.g. jj)
designed for users who manage branches with external tools (e.g. jj, Sapling, ghstack, git-town, etc...)

Comment thread cmd/link.go
Comment on lines +81 to +123
if err := pushBranchArgs(cfg, opts, args); err != nil {
return err
}

// Phase 2: Find existing PRs for all args (don't create yet)
found, err := findExistingPRs(cfg, client, args)
if err != nil {
return err
}

// Phase 3: Pre-validate the stack — check that adding these PRs won't
// conflict with existing stacks before creating any new PRs.
// Also fetches stacks for reuse in the upsert phase.
knownPRNumbers := make([]int, 0, len(found))
for _, r := range found {
if r != nil {
knownPRNumbers = append(knownPRNumbers, r.prNumber)
}
}
stacks, err := listStacksSafe(cfg, client)
if err != nil {
return err
}
if len(knownPRNumbers) > 0 {
if err := prevalidateStack(cfg, stacks, knownPRNumbers); err != nil {
return err
}
}

// Phase 4: Create PRs for branches that don't have one yet
resolved, err := createMissingPRs(cfg, client, opts, args, found)
if err != nil {
return err
}

// Phase 5: Fix base branches for existing PRs with wrong bases
fixBaseBranches(cfg, client, opts, resolved)

// Phase 6: Upsert the stack (reuse stacks from phase 3)
prNumbers := make([]int, len(resolved))
for i, r := range resolved {
prNumbers[i] = r.prNumber
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do you think we should add any console logging info to the user while these events are happening so it appears less like it's timing out or freezing?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good call, added additional logging in 499fdb8

Comment thread cmd/link.go Outdated
Comment on lines +293 to +294
// createMissingPRs creates PRs for args that don't have one yet.
// Returns the fully resolved list with all args mapped to PRs.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should this be branches instead of args?

Suggested change
// createMissingPRs creates PRs for args that don't have one yet.
// Returns the fully resolved list with all args mapped to PRs.
// createMissingPRs creates PRs for branches that don't have one yet.
// Returns the fully resolved list with all branches mapped to PRs.

Copy link
Copy Markdown

@Lukeghenco Lukeghenco left a comment

Choose a reason for hiding this comment

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

A couple of copy changes that we may or may not want to make, but the code flows look correct.

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.

3 participants