feat: use GitHub's native stacked PRs where enabled#14840
Conversation
9f7d0d2 to
f7d3c07
Compare
There was a problem hiding this comment.
Pull request overview
Adds support for GitHub’s preview “native stacked pull requests” by detecting availability per repo and registering GitButler-published PR chains as GitHub stacks when possible, while keeping existing footer-based behavior for other forges and unsupported repos.
Changes:
- Introduces a
but_github::stacksmodule to look up and (best-effort) create/extend native stacks on GitHub. - Makes
but_forge::sync_reviewsoperate on a single bottom-to-top stack batch, removing/writing footers based on native-stack availability and handling fork-headed PRs. - Routes desktop footer updates through the existing backend command (
update_review_footers) and adjusts target reconciliation to sync per stack.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/but/src/command/legacy/push.rs | Reconciles review target branches by syncing each stack as its own sync_reviews batch. |
| crates/but-github/src/stacks.rs | Adds GitHub native stacks REST wrapper (lookup, ensure) plus negative caching. |
| crates/but-github/src/lib.rs | Exposes the new stacks module. |
| crates/but-github/src/client.rs | Broadens client field visibility to support the stacks module implementation. |
| crates/but-forge/src/review.rs | Updates sync_reviews to treat each batch as a single stack and optionally register native GitHub stacks while adjusting footer behavior. |
| crates/but-forge/Cargo.toml | Adds tracing dependency needed for new warning logs. |
| crates/but-api/src/legacy/forge.rs | Derives base+push forge repo info and passes it into sync_reviews for fork detection. |
| Cargo.lock | Locks tracing addition for but-forge. |
| apps/desktop/src/lib/forge/shared/prFooter.ts | Removes TS-side footer writers; sends stack updates to backend as bottom-to-top ForgeReviewUpdate[]. |
| apps/desktop/src/lib/forge/shared/prFooter.test.ts | Replaces footer rendering tests with ordering/target-branch chaining tests for backend-driven updates. |
| apps/desktop/src/lib/forge/prService.svelte.ts | Adds an RTK mutation + service helper for update_review_footers command. |
| apps/desktop/src/components/forge/ReviewCreation.svelte | Switches to the renamed stack-info updater export. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f7d0d2dec
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
GitHub's stacking preview models stacks exactly like GitButler (each PR targets the branch below), so published PRs can be registered as-is via the new stacks REST endpoints. sync_reviews now probes the feature per repository (404 means not enabled; negative results cached) and, where available, registers the stack with GitHub instead of writing the description footer, stripping any footer left from before. Fork-headed stacks keep footers since cross-fork stacks are unsupported, as do GitLab/Bitbucket. Each sync_reviews batch is now defined as one stack ordered bottom to top; the push path syncs per stack accordingly, and the desktop footer writers route through the same backend command so the footer-vs-native decision lives in one place.
f7d3c07 to
173de59
Compare
| // Push-only target updates carry no footer content and leave bodies alone. | ||
| let footer_sync = if reviews.iter().any(|r| !r.unit_symbol.is_empty()) { | ||
| FooterSync::Write | ||
| } else { | ||
| FooterSync::KeepBody |
GitHub is rolling out native stacked pull requests as a per-repository preview. Its model matches ours exactly — each PR targets the branch of the PR below it — so the PRs GitButler publishes can be registered as a stack as-is.
What changed
but_github::stacksmodule wrapping the stacks REST endpoints:lookup(find the stack containing a PR) andensure(create a stack, or append missing members on top).sync_reviewsis the single decision point. On a GitHub repo with the feature enabled it registers the stack natively and removes description footers (GitHub renders the stack itself, including any footers left from before the repo was enabled). Everywhere else — GitLab, Bitbucket, forks (cross-fork stacks are unsupported), non-enabled repos — behavior is unchanged.Decisions
sync_reviewsbatch is now explicitly one stack ordered bottom-to-top; the post-push target reconciliation syncs per stack accordingly.prFooter.tsnow routes through the existingupdate_review_footerscommand, continuing the migration of forge logic to the Rust side. Command signatures are unchanged, so no SDK regeneration.FooterSyncmode + helper, which also deduplicates the previously triplicated body-update block across the three forge arms.Not included (deferred): unstacking on tear-off/reorder (the API can only dissolve a stack, not remove a single PR), surfacing native-stack state in the UI, and a "merge whole stack" action.