git spr turns every commit on a branch into its own stacked pull request
Stacked Pull Requests on GitHub
At a glance
- What is it?
- A Go tool that keeps a stack of GitHub pull requests in sync with a linear commit history, so a feature branch becomes a series of small, independently reviewable, correctly ordered PRs. The interesting design decision is that it merges by folding commits back together, which avoids redundant CI runs and is also the part that will surprise you.
- Who is it for?
- Adopt git spr if your team reviews large features as a series of small pull requests and you are tired of maintaining the branch and rebase chains by hand, especially if your CI cost per run is high. Do not adopt it if your workflow depends on a shared base branch moving under your stack, or if the pull requests are meant to be reviewed and merged independently by different people, because the merge behaviour folds commits together and closes the intermediate pull requests.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One branch, many pull requests, no branch juggling
The core idea of git spr is that you write your work as ordinary commits on a single branch, and the tool turns each commit into its own pull request on GitHub, kept in sync, correctly ordered and ready to merge.
git commit -m "Add user authentication"
git commit -m "Add login page"
git commit -m "Add session management"
git spr update # creates 3 pull requests, stacked in order
git spr status # show status of your stack
git spr merge # merge everything that's readyThe commit subject becomes the pull request title and the commit body becomes the description. There is no need to create branches or call `git push`; `git spr update` handles it. Amend a commit and run the same command again to sync the change into the existing pull request.
The argument the README makes for this workflow is not about speed but about review quality. A 50 line change gets meaningful feedback, a 500 line change gets an approval. The second claim is the one that matters for a team with expensive CI, because a stacked series means each pull request runs the checks on its own small diff rather than on the whole feature.
The tool also removes the mechanics that stacked pull requests are usually built on. There is no `feature-part-1`, no `feature-part-2`, no manual rebase of one onto the other and no conflict resolution between them. It works with native GitHub, with no extra services and no custom merge bot, which is the constraint that keeps it viable inside an organisation that will not run new infrastructure for a git workflow.
The merge step folds commits back into one pull request
This is the part of the design that deserves the most thought, because it is not what the name of the tool suggests.
The README describes what `git spr merge` does: it finds the top mergeable pull request in the stack, combines all commits up to it into a single pull request, merges it, and closes the intermediate pull requests. The stated reason is that this avoids triggering redundant CI runs.
That reason is sound. If you merged three stacked pull requests one at a time in order, each merge would rebase or re-target the ones above it, and each rebase would trigger a fresh CI run on the remaining pull requests. The result is three merges and a storm of redundant checks. Collapsing the stack into one pull request and merging once means one run.
The consequence is that the intermediate pull requests never get merged on their own. Their reviews and comments still exist and still count for the approval requirement, but what lands on the base branch is one squashed result rather than three separately merged commits. A team that cares about the individual commits surviving into the history, or that wants different reviewers to approve different parts and have those approvals recorded as separate merges, will find that the tool does not do that.
The `--count N` flag limits the merge to the bottom N pull requests, and the output shows what is left behind, with the status bits on the remaining stack member.
Four status bits decide whether a pull request is mergeable
Each pull request in the stack is displayed with four status bits, and understanding them is most of understanding the tool.
The bits are checks, approval, conflicts and stack. Checks means the CI checks pass, with a separate state for pending, failed and not required. Approval means the pull request is approved, with states for not approved and not required. Conflicts means no merge conflicts. Stack means all pull requests below it in the stack are ready, and a blocked stack shows as a failure, which is what stops you from merging a pull request whose dependency has not landed.
The stack bit is the one that has no equivalent in the GitHub UI. A pull request can be green, approved and conflict free and still be unmergeable because the pull request it is based on has not been merged yet. Encoding that in the display is the point of a stack aware tool.
Which checks count is configurable through `requireChecks`, `requiredChecks` and `requireApproval` in `.spr.yml`. The `requiredChecks` behaviour is the useful part: when it lists specific check names, only those checks are evaluated and all others are ignored. That solves a common problem where an optional linter or a deploy preview fails and blocks a merge for no good reason.
The global flags support the same need for detail. `--detail` shows the status bit headers instead of only the icons, `--verbose` logs the git commands and the GitHub API calls, and `--debug` and `--profile` go further. For a tool that rewrites branches under you, being able to see exactly which git commands it ran is not a luxury.
Amend, edit, sync and the WIP escape hatch
The command set is small and each verb has a clear job.
`git spr amend` picks which commit in the stack to amend, showing the list with short hashes and subjects so you can choose by number, and `--update` runs the update afterwards. `git spr edit` starts an interactive rebase session on a specific commit, with `--done` to finish and `--abort` to cancel, and it accepts `-u` to also update. `git spr sync` pulls remote changes into your local stack, which is what you want after pull requests have been merged or updated on GitHub. `git spr check` runs the pre-merge checks configured by `mergeCheck`, and `git spr version` prints version information.
The interactive prompts show the stack in a compact form.
git spr amend
3 : 5cba235d : Feature 3
2 : 4dc2c5b2 : Feature 2
1 : 9d1b8193 : Feature 1
Commit to amend [1-3]: 2Two workflow details are worth knowing before you start. The first is the WIP convention: prefixing a commit message with WIP skips pull request creation for that commit, and removing the prefix later makes it eligible. That lets you keep a scratch commit in the middle of a stack without it becoming a pull request.
The second is `--no-rebase`, which disables rebasing and also honours a `SPR_NOREBASE` environment variable. Rebasing is what keeps the stack aligned with its base, and there are workflows where you need to turn it off, so the escape hatch is documented rather than hidden.
`--count` on update limits the operation to a specific number of pull requests from the bottom of the stack, and `--reviewer` adds reviewers to newly created pull requests.
Configuration, installation and the shape of the codebase
Configuration is created automatically on first run, so there is no setup step to get in the way of trying it. Repository configuration lives in `.spr.yml` at the repository root, and user configuration lives in `~/.spr.yml`. The split lets a team share the check and approval requirements through version control while a developer keeps personal preferences at the user level.
Installation has the usual spread of options. Homebrew, with a tap first, Nix, either installed as a profile or run directly with `nix run`, an apt repository, pre-compiled binaries from the releases page, and a `make bin` target that needs goreleaser and outputs to `dist/`.
brew tap ejoffe/homebrew-tap
brew install ejoffe/tap/sprThe Go module is `github.com/ejoffe/spr` and it requires Go 1.21 or newer. The dependency list tells you a lot about the implementation. `go-git` provides the git operations, `urfave/cli/v2` and `jessevdk/go-flags` provide the command line parsing, `rs/zerolog` handles the logging, `golang.org/x/oauth2` handles authentication, `yaml.v3` reads the configuration, and `stretchr/testify` is there for tests.
Two of the dependencies are `github.com/inigolabs/fezzik` and `github.com/ejoffe/rake`. Using a git library rather than shelling out to the `git` binary is a deliberate choice, since it makes behaviour consistent across platforms, and it also means the tool can only do what the library exposes.
The repository layout follows the structure of the tool: `git/` for the git operations, `github/` for the API calls, `spr/` for the core stack logic, `cmd/` for the command line entry points, `config/` for the configuration structures, `terminal/` for output rendering, `pretty/` for formatting, and `tools/` for auxiliary commands. There is a `.goreleaser.yml` for the release pipeline, a `flake.nix` for Nix packaging, and a `Makefile` whose only target builds a snapshot binary with goreleaser.
Licence, release cadence and where the risk sits
The project is MIT licensed, and the badge in the README says so. For a tool that runs inside a developer's commit loop, a permissive licence is the right default and the lack of friction is worth noting if your organisation has a legal review step for new developer tooling.
The release history is regular. v0.17.6 was published on 2026-04-22, v0.17.5 on 2026-04-18 and v0.17.4 on 2026-04-11, three releases in a fortnight. The last push to the repository was on 2026-04-22 and the repository is not archived. The version is still below 1.0, which for a tool that rewrites branches means the command surface may still change, and the low cadence of the last few releases relative to the earlier ones suggests you should read the release notes before upgrading in the middle of a stack.
The risk worth naming is not the code, it is the model. The tool keeps your local branch, the remote branches and a set of pull requests in a specific relationship, and it maintains that relationship by rebasing. If you also use a tool that rewrites history, or if your base branch receives commits while your stack is open, the two can disagree about what the stack looks like. `git spr sync` exists to resolve that, and `--no-rebase` exists for when rebasing is not what you want, but the coordination is still manual.
A second risk is quieter. `git spr update` pushes branches to GitHub and creates pull requests, and `git spr merge` merges them. The GitHub API calls need credentials, and the tool ships with the OAuth library, so the authentication is a setup step the documentation covers rather than something to discover. The check to run first is a small stack on a scratch branch, where you can see exactly what branches appear on GitHub and exactly what a merge does to the history before you point the tool at work that matters.
Editorial conclusion
Adopt git spr if your team reviews large features as a series of small pull requests and you are tired of maintaining the branch and rebase chains by hand, especially if your CI cost per run is high. Do not adopt it if your workflow depends on a shared base branch moving under your stack, or if the pull requests are meant to be reviewed and merged independently by different people, because the merge behaviour folds commits together and closes the intermediate pull requests. Verify first that a commit subject becomes the pull request title and the commit body becomes the description, that the check and approval requirements in .spr.yml match what you consider mergeable, and that a test merge on a throwaway stack behaves the way you expect before you point it at a real feature branch.
Frequently asked questions
What does git spr do?
It manages stacked pull requests on GitHub. You write commits on a single branch and git spr update turns each commit into its own pull request, kept in sync, correctly ordered and ready to merge.
How do I install git spr?
The README lists brew install ejoffe/tap/spr for macOS and Linux, nix profile install github:ejoffe/spr or nix run github:ejoffe/spr, an apt repository, pre-compiled binaries from the releases page, and make bin from source, which requires goreleaser.
What do the four status bits in git spr status mean?
They are checks, approval, conflicts and stack. The stack bit is unique to stacked pull requests: it shows whether every pull request below this one in the stack is ready, so a green pull request can still be blocked by its dependency.
What happens when I run git spr merge?
It finds the top mergeable pull request in the stack, combines all commits up to it into a single pull request, merges it, and closes the intermediate pull requests, which avoids redundant CI runs. Use --count N to merge only the bottom N pull requests.
Where is git spr configured and how do I skip a pull request for a work in progress commit?
Repository configuration lives in .spr.yml at the repo root and user configuration in ~/.spr.yml, and both are created on first run. Prefixing a commit message with WIP skips pull request creation for that commit until you remove the prefix.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ejoffe-spr)Was this useful?