Stacked git branches with git-spice

Ever find yourself working on a feature where the choice is between one massive PR that nobody wants to review, or waiting for each small change to be merged before you can start the next? Some features split naturally into smaller PRs, but keeping those PRs in sync as review feedback comes in is painful.
git-spice is a command line tool written in Go that makes this much easier. It lets you create branches that build on top of each other while keeping individual changes focused and reviewable. It also makes it very easy to re-stack the branches, keeping them in sync, along with other quality of life improvements.
What is Branch Stacking?
Think of it like this: instead of cramming everything into one huge PR, you create a series of small, connected PRs that tell a story. Each one builds on the previous, but reviewers can understand and approve them individually.
gitGraph
commit id: "main"
branch feat/payment-abstraction
checkout feat/payment-abstraction
commit id: "Add payment provider interface"
branch feat/stripe-integration
checkout feat/stripe-integration
commit id: "Implement Stripe provider"
branch feat/payment-ui
checkout feat/payment-ui
commit id: "Add payment selection UI"
How git-spice Works
git-spice operates as a layer above Git, tracking branch relationships and automating dependency management. It stores metadata locally in your repository, requiring no external services or configuration changes.
gs command often conflicts with ghostscript. I created an alias spice for git-spice instead.Basic Workflow
# Initialize git-spice in the repository (optional, it happens automatically when needed)
gs repo init
# Create the base branch
gs branch create payment-abstraction
git commit -m "Add payment provider interface"
# Create dependent branches automatically
gs branch create stripe-integration
git commit -m "Implement Stripe provider"
gs branch create payment-ui
git commit -m "Add payment selection UI"Each gs branch create command establishes the current branch as the parent for the new branch, building the dependency chain automatically.
Handling Changes and Re-stacking
The key advantage becomes apparent when handling review feedback:
# Return to base branch to address review comments
gs branch checkout payment-abstraction
git commit -m "Fix payment validation based on review"
# Automatically rebase all dependent branches
gs stack restackThe restack command identifies all branches that depend on the current branch and rebases them to include the new changes. This saves a lot of manual rebasing.
Submitting Pull Requests
# Submit entire stack with proper dependencies
gs stack submit
# Or submit individual branches with correct base references
gs branch submitgit-spice handles PR creation with appropriate base branch references and dependency links, ensuring reviewers understand the relationship between changes.
Getting started
Install it following the installation guide, pick a feature that splits naturally, and stick to the three commands above: gs branch create, gs stack restack, gs stack submit. The rest of the CLI can wait. It works with GitHub and GitLab, among others, without changing how your team merges.
Limitation: Squash-and-Merge
One key limitation to understand is how git-spice handles squash-and-merge operations. When a PR is squash-merged into the main branch, GitHub/GitLab replace all commits in that PR with a single commit (squashing) that has a different hash.
The problem is that the branches stacked on top of it still reference the old, unsquashed commits. The hosting platforms don’t automatically reconcile this new squashed commit with the dependent branches, even though the content is identical.
So after a squash-merge, run gs repo sync: it notices the merged PR, deletes the local branch, and points the branches above it at the new base. Then gs stack restack (or gs repo sync --restack) rebases them and updates their PRs.
Also worth knowing: depending on your repository settings, restacking will dismiss existing approvals on the PRs above.