gh stack sync vs rebase: Understanding the Difference in GitHub's gh-stack
gh stack sync is a full-cycle remote synchronization command that fetches, pushes, and updates GitHub's stack metadata, while gh stack rebase performs local-only cascading rebases to rearrange commits without network operations.
When managing stacked pull requests with the github/gh-stack extension, choosing between these commands determines whether you are aligning your local repository with remote state or merely fixing local history. Both utilize the same cascadeRebase helper internally, but they differ fundamentally in their scope, network behavior, and side effects.
What Does gh stack sync Do?
The gh stack sync command, implemented in cmd/sync.go within the runSync function, orchestrates a comprehensive synchronization between your local branches and the remote GitHub stack object. According to the command's long help text (lines 27-41), its primary goal is to bring the local stack and remote stack representation into complete agreement.
The implementation follows this sequence:
- Fetch the latest remote refs from GitHub
- Reconcile any remote-side stack changes via
reconcileRemoteStack, pulling down new PR branches if they exist - Fast-forward branches that are behind their remote tracking refs
- Cascade-rebase the stack if necessary, invoking the same
cascadeRebasehelper used by the rebase command - Push all branches atomically with
--force-with-lease - Sync PR state and link pull requests into the remote stack object
- Prune merged local branches when invoked with
--prune
When conflicts occur during the cascade-rebase phase, the command aborts immediately and instructs the user to resolve them using gh stack rebase (lines 51-53). Upon successful completion, the command outputs either "Stack synced" (when the remote object is updated) or "Branches synced" (when only local branches are pushed), as implemented in lines 98-104.
What Does gh stack rebase Do?
Defined in cmd/rebase.go and executed via the runRebase function, gh stack rebase is strictly a local operation that rearranges commit history. As described in lines 53-60, it performs a cascading rebase so that each branch in the stack sits on top of its predecessor (or the trunk) without ever fetching from or pushing to the remote.
Key implementation characteristics include:
- No network operations: Skips fetching and pushing entirely
- Cascading execution: Calls
cascadeRebase(lines 150-160) to sequentially rebase each branch onto its parent - Conflict state management: On conflict, writes a state file named
gh-stack-rebase-state(lines 62-66) to allow users to continue or abort later - Scope control: Supports
--downstack(trunk to current),--upstack(current to top), and--no-trunkflags (lines 48-55) to limit the rebase range
After a successful rebase, the command reminds you to run gh stack push to propagate changes, explicitly avoiding any modification of remote PR metadata.
Key Differences: Remote Synchronization vs Local History
| Aspect | gh stack sync |
gh stack rebase |
|---|---|---|
| Primary Goal | Synchronize local state with remote GitHub stack object | Locally rearrange commits via cascading rebase |
| Network Operations | Fetches from remote and pushes with --force-with-lease |
No fetch or push; local Git operations only |
| Remote Metadata | Updates and links PRs into the remote stack object | Does not modify remote PR state or stack file |
| Conflict Resolution | Aborts on conflict; defers to gh stack rebase |
Saves state to file; supports --continue and --abort |
| Typical Result | Repository matches remote GitHub state exactly | Repository rebased locally; requires manual push |
When to Use Each Command
Use gh stack sync When You Need to Align with GitHub
Run this command when you need to ensure GitHub reflects your current local work. This includes scenarios where you have updated commits locally and need to push them, when teammates have modified the remote stack and you must reconcile changes, or when you want to prune merged branches using the --prune flag.
Use gh stack rebase When You Need to Fix Local History
Execute this command when resolving diverging histories after pulling from upstream, when reordering commits within your stack before submitting, or when continuing a previously conflicted rebase using --continue. The --downstack and --upstack flags allow you to limit the operation to specific portions of the stack without affecting the entire chain.
Handling Conflicts: Different Workflows
The commands diverge significantly in their approach to merge conflicts. In cmd/sync.go (lines 51-53), when the embedded cascadeRebase call encounters conflicts, gh stack sync immediately aborts the entire operation and directs you to run gh stack rebase for manual resolution.
Conversely, cmd/rebase.go (lines 62-66) handles conflicts by writing the gh-stack-rebase-state file to disk, allowing you to resolve conflicts at your own pace and resume with gh stack rebase --continue or revert entirely with gh stack rebase --abort.
Practical Code Examples
# Full synchronization: fetch, reconcile, rebase, push, and update PRs
gh stack sync
gh stack sync --prune
# Local-only rebase operations
gh stack rebase
gh stack rebase --downstack
gh stack rebase --upstack
gh stack rebase --no-trunk
# Conflict resolution during rebase
gh stack rebase --continue
gh stack rebase --abort
Summary
gh stack syncincmd/sync.gois the comprehensive command that fetches remote changes, reconciles the stack object, pushes branches with--force-with-lease, and updates GitHub metadatagh stack rebaseincmd/rebase.gois the local-only tool for cascading rebases that uses thecascadeRebasehelper without network operations- Sync aborts on conflicts and requires rebase for resolution; rebase provides
--continueand--abortworkflows via thegh-stack-rebase-statepersistence file - Use sync to align local and remote states; use rebase to manipulate local commit history before pushing
Frequently Asked Questions
Can I run gh stack rebase after gh stack sync fails?
Yes. When gh stack sync encounters rebase conflicts during its cascade phase (as implemented in cmd/sync.go lines 51-53), it aborts the entire synchronization and explicitly instructs you to run gh stack rebase to resolve the conflicts manually. After resolving the conflicts and completing the rebase with --continue, you can then run gh stack sync again to push the changes and update the remote stack object.
Does gh stack sync automatically prune merged branches?
Only when invoked with the --prune flag. According to the implementation in cmd/sync.go, the sync command includes an optional pruning step that deletes local branches for PRs that have been merged on GitHub. Without this flag, the command synchronizes active branches but leaves merged branches intact in your local repository.
Why does gh stack rebase not update my pull requests on GitHub?
Because gh stack rebase (defined in cmd/rebase.go lines 53-60) performs strictly local Git operations and deliberately does not push to the remote. After completing the cascading rebase, the command simply prints a reminder to push using gh stack push or gh stack submit. This design allows you to verify the rebased history before exposing rewritten commits to collaborators and updating the remote PR stack metadata.
What happens to the stack state if I abort a rebase?
When you run gh stack rebase --abort, the command removes the gh-stack-rebase-state persistence file and restores the branches to their original positions recorded before the rebase began. This cleanup is managed within the runRebase function in cmd/rebase.go (lines 62-66), ensuring your repository returns to a clean state without partial rebase artifacts.
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 →