How gh-stack Manages Stack Updates When Base Pull Requests Are Merged
When a base PR in a gh-stack chain is merged, the tool automatically detects the merged state, skips closed ancestors, and rebases downstream branches onto the new base to maintain a linear stack.
Merging the bottom pull request in a stacked chain traditionally forces manual rebasing for every subsequent branch. The github/gh-stack CLI solves this through automated detection and rebase logic that preserves stack integrity without manual intervention.
Detecting Merged Base Pull Requests
The update process begins by refreshing the state of every PR in the stack via the GitHub API. In cmd/utils.go, the helper function stackNeedsRebase inspects each pull request's metadata to determine if it requires updating.
The function specifically checks for the NeedsRebase flag and explicitly filters out any PRs that are already merged or closed. This prevents the tool from attempting to rebase branches that have already been integrated into the default branch, as implemented around line 1194 in cmd/utils.go.
Resolving the New Base After a Merge
Once merged PRs are identified, gh-stack must determine where to attach the remaining branches. The core logic resides in resolveRebaseBase within cmd/rebase.go (around line 192).
This function walks up the stack hierarchy looking for the first non-merged ancestor. When it encounters a merged PR, it treats that merged commit as the new effective base and continues ascending until it finds either the next unapplied PR or the repository's default branch. The algorithm ensures that downstream branches rebase onto the actual commit that entered the mainline, not onto the PR branch that no longer exists.
The test suite validates this behavior in cmd/rebase_test.go with TestRebase_MergedBranch_UsesOnto (line 633), confirming that merged ancestors are correctly skipped during the rebase target resolution.
Executing Stack Updates via Rebase or Fast-Forward
After identifying the correct new base, gh-stack applies the changes through internal/modify/apply.go. Around line 409, the apply logic distinguishes between fast-forward scenarios (where the merge was a simple fast-forward) and full interactive rebases.
The tool executes git rebase operations only for branches that are not already merged, ensuring that closed PRs remain untouched while active branches receive clean, linear history. This phase also handles branch pointer updates and conflict detection before rewriting history.
Handling Merge Queue Integration
For repositories utilizing GitHub's merge queue, gh-stack respects queue ordering to prevent invalid states. The detection logic in internal/github/merge_async.go (around line 207) identifies when a PR is managed by the merge queue via GraphQL API calls.
When a base PR is merged through the queue, subsequent PRs are rebased onto the queued target rather than the final merged commit. This ensures that the stack remains valid according to the merge queue's sequencing constraints, preventing premature rebases that could conflict with pending queue operations.
Practical Examples: Updating Your Stack After a Base PR Merge
When the bottom PR of your stack merges into the default branch, use the following workflow to synchronize the remaining branches:
# Refresh the stack state from GitHub
$ gh stack sync
# Automatically rebase remaining PRs onto the new base
$ gh stack rebase
For programmatic usage within Go applications interacting with gh-stack internals:
// Resolve the current stack configuration
stack, target, err := resolveMergeStack(cfg, client, []string{})
if err != nil {
log.Fatal(err)
}
// Merge the target PR asynchronously; downstream PRs will be rebased automatically
res, err := client.MergeStackAsync(target.prNumber, "squash", github.MergeActionDirectMerge)
After successful execution, the stack metadata stored in .git/gh-stack is updated via internal/stack/stack.go to reflect the new branch relationships and base commit SHAs.
Summary
- Automatic Detection: The
stackNeedsRebasefunction incmd/utils.goidentifies merged or closed PRs and excludes them from rebase operations. - Smart Base Resolution:
resolveRebaseBaseincmd/rebase.gowalks the stack to find the first non-merged ancestor, using merged commits as the new rebase target. - Selective Rebase Execution:
internal/modify/apply.goperforms rebases only on active branches, preserving merge queue order and avoiding rewritten history for closed PRs. - Merge Queue Support:
internal/github/merge_async.godetects merge queue participation and adjusts rebase targets to respect queue sequencing.
Frequently Asked Questions
How does gh-stack detect when a base PR has been merged?
The tool queries the GitHub API during gh stack sync operations to refresh each PR's state. The stackNeedsRebase helper in cmd/utils.go examines the merged and closed status flags, returning early for any PRs that have already been integrated into the default branch.
What happens to downstream PRs when the base is merged?
Downstream PRs are automatically rebased onto the new base commit. The resolveRebaseBase function identifies the merged PR's final commit SHA and treats it as the new base for subsequent branches, ensuring the stack remains linear and attached to the mainline history.
Does gh-stack support GitHub's merge queue?
Yes. When internal/github/merge_async.go detects that a PR is part of GitHub's merge queue, the rebase logic adjusts to target the queued merge commit rather than the current default branch head. This preserves queue validity and prevents conflicts with pending merges.
Where does gh-stack store updated stack metadata?
After successful rebasing, the tool writes the new stack configuration—including updated base SHAs and branch relationships—to the local metadata file managed by internal/stack/stack.go, typically located at .git/gh-stack within the repository root.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →