Stacked PRs for GitHub
This is a command-line tool that helps you create multiple GitHub
pull requests (PRs) all at once, with a stacked order of dependencies.
Imagine that we have a change A and a change B depending on A, and we
would like to get them both reviewed. Without stacked PRs one would have to
create two PRs: A and A+B. The second PR would be difficult to review as it
includes all the changes simultaneously. With stacked PRs the first PR will
have only the change A, and the second PR will only have the change B. With
stacked PRs one can group related changes together making them easier to
review.
Example:

Installation
Dependencies
This is a non-comprehensive list of dependencies required by stack-pr.py:
- Install
gh, e.g., brew install gh on MacOS.
- Run
gh auth login with SSH
Installation with pipx
To install via pipx run:
pipx install stack-pr
Manual installation from source
Manually, you can clone the repository and run the following command:
pipx install .
Usage
stack-pr allows you to work with stacked PRs: submit, view, and land them.
Basic Workflow
The most common workflow is simple:
- Create a feature branch from
main:
git checkout main
git pull
git checkout -b my-feature
- Make your changes and create multiple commits (one commit per PR you want to create)
git commit -m "First change"
git commit -m "Second change"
- Review what will be in your stack:
stack-pr view
- Create/update the stack of PRs:
stack-pr submit
Note: export is an alias for submit.
- To update any PR in the stack:
- Amend the corresponding commit
- Run
stack-pr view to verify your changes
- Run
stack-pr submit again
- To rebase your stack on the latest main:
git checkout my-feature
git pull origin main
git rebase main
stack-pr submit
- When your PRs are ready to merge, you have two options:
Option A: Using stack-pr land:
stack-pr land
This will:
- Merge the bottom-most PR in your stack
- Automatically rebase your remaining PRs
- You can run
stack-pr land again to merge the next PR once CI passes
Option B: Using GitHub web interface:
That's it!
Pro-tip: Run stack-pr view frequently - it's a safe command that helps you understand the current state of your stack and catch any potential issues early.
Commands
stack-pr has five main commands:
submit (or export) - create a new stack of PRs from the given set of
commits. One can think of this as "push my local changes to the corresponding
remote branches and update the corresponding PRs (or create new PRs if they
don't exist yet)".
view - inspect the given set of commits and find the linked PRs. This
command does not push any changes anywhere and does not change any commits.
It can be used to examine what other commands did or will do.
abandon - remove all stack metadata from the given set of commits. Apart
from removing the metadata from the affected commits, this command deletes
the corresponding local and remote branches and closes the PRs.
land - merge the bottom-most PR in the current stack and rebase the rest of
the stack on the latest main.
config - set configuration values in the config file. Similar to git config,
it takes a setting in the format <section>.<key>=<value> and updates the
config file (.stack-pr.cfg by default).
A usual workflow is the following:
while not ready to merge:
make local changes
commit to local git repo or amend existing commits
create or update the stack with `stack-pr.py submit`
merge changes with `stack-pr.py land`
You can also use view at any point to examine the current state, and
abandon to drop the stack.
Under the hood the tool creates and maintains branches named
$USERNAME/stack/$BRANCH_NUM (the name pattern can be customized via
--branch-name-template option) and embeds stack metadata into commit messages,
but you are not supposed to work with those branches or edit that metadata
manually. I.e. instead of pushing to these branches you should use submit,
instead of deleting them you should use abandon and instead of merging them
you should use land.
The tool looks at commits in the range BASE..HEAD and creates a stack of PRs
to apply these commits to TARGET. By default, BASE is main (local
branch), HEAD is the git revision HEAD, and TARGET is main on remote
(i.e. origin/main). These parameters can be changed with options -B, -H,
and -T respectively and accept the standard git notation: e.g. one can use
-B HEAD~2, to create a stack from the last two commits.
Example
The first step before creating a stack of PRs is to double-check the changes
we’re going to post.
By default stack-pr will look at commits in main..HEAD range and will create
a PR for every commit in that range.
For instance, if we have
**cc932b71c** (**my-feature**) Optimized navigation algorithms for deep space travel
**3475c898f** Fixed zero-gravity coffee spill bug in beverage dispenser
**99c4cd9a7** Added warp drive functionality to spaceship engine.
**d2b7bcf87** (**origin/main, main**) Added module for deploying remote space probes
Then the tool will consider the top three commits as changes, for which we’re
trying to create a stack.
Pro-tip: a convenient way to see what commits will be considered by
default is the following command:
alias githist='git log --abbrev-commit --oneline $(git merge-base origin/main HEAD)^..HEAD'
We can double-check that by running the script with view command - it is
always a safe command to run:
...
VIEW
**Stack:**
* **cc932b71** (No PR): Optimized navigation algorithms for deep space travel
* **3475c898** (No PR): Fixed zero-gravity coffee spill bug in beverage dispenser
* **99c4cd9a** (No PR): Added warp drive functionality to spaceship engine.
SUCCESS!
If everything looks correct, we can now submit the stack, i.e. create all the
corresponding PRs and cross-link them. To do that, we run the tool with
submit command:
...
SUCCESS!
The command accepts a couple of options that might be useful, namely:
--draft - mark all created PRs as draft. This helps to avoid over-burdening
CI.
--draft-bitmask - mark select PRs in a stack as draft using a bitmask where
1 indicates draft, and 0 indicates non-draft.
For example --draft-bitmask 0010 to make the third PR a draft in a stack
of four.
The length of the bitmask must match the number of stacked PRs.
Overridden by --draft when passed.
--reviewer="handle1,handle2" - assign specified reviewers.
If the command succeeded, we should see “SUCCESS!” in the end, and we can now
run view again to look at the new stack:
...
VIEW
**Stack:**
* **cc932b71** (
* **3475c898** (
* **99c4cd9a** (
SUCCESS!
We can also go to github and check our PRs there:

If we need to make changes to any of the PRs (e.g. to address the review
feedback), we simply amend the desired changes to the appropriate git commits
and run submit again. If needed, we can rearrange commits or add new ones.
submit simply syncs the local changes with the corresponding PRs. This is why
we use the same stack-pr submit command when we create a new stack, rebase our
changes on the latest main, update any PR in the stack, add new commits to the
stack, or rearrange commits in the stack.
When we are ready to merge our changes, we use land command.
LAND
Stack:
* cc932b71 (
* 3475c898 (
* 99c4cd9a (
Landing 99c4cd9a (
...
Rebasing 3475c898 (
...
Rebasing cc932b71 (
...
SUCCESS!
This command lands the first PR of the stack and rebases the rest. If we run
view command after land we will find the remaining, not yet-landed PRs
there:
VIEW
**Stack:**
* **8177f347** (
* **35c429c8** (
This way we can land all the PRs from the stack one by one.
Specifying custom commit ranges
The example above used the default commit range - main..HEAD, but you can
specify a custom range too. Below are several commonly useful invocations of
the script:
stack-pr submit -B HEAD~5
stack-pr submit -B origin/main
stack-pr submit -H HEAD~2
These options work for all script commands (and it’s recommended to first use
them with view to double check the result). It is possible to mix and match
them too - e.g. one can first submit the stack for the last 5 commits and then
land first three of them:
stack-pr view -B HEAD~5
stack-pr submit -B HEAD~5
stack-pr view -B HEAD~5 -H HEAD~2
stack-pr land -B HEAD~5 -H HEAD~2
Note that generally one doesn't need to specify the base and head branches
explicitly - stack-pr will figure out the correct range based on the current
branch and the remote main by default.
Command Line Options Reference
Common Arguments
These arguments can be used with any subcommand:
-R, --remote: Remote name (default: "origin")
-B, --base: Local base branch
-H, --head: Local head branch (default: "HEAD")
-T, --target: Remote target branch (default: "main")
--hyperlinks/--no-hyperlinks: Enable/disable hyperlink support (default: enabled)
-V, --verbose: Enable verbose output from Git subcommands (default: false)
--branch-name-template: Template for generated branch names (default: "$USERNAME/stack"). The following variables are supported:
$USERNAME: The username of the current user
$BRANCH: The current branch name
$ID: The location for the ID of the branch. The ID is determined by the order of creation of the branches. If $ID is not found in the template, the template will be appended with /$ID.
Subcommands
submit (alias: export)
Submit a stack of PRs.
Options:
--keep-body: Keep current PR body, only update cross-links (default: false)
-d, --draft: Submit PRs in draft mode (default: false)
--draft-bitmask: Bitmask for setting draft status per PR
--reviewer: List of reviewers for the PRs (default: from $STACK_PR_DEFAULT_REVIEWER or config)
-s, --stash: Stash all uncommitted changes before submitting the PR
land
Land the bottom-most PR in the current stack.
If the land.style config option has the disable value, this command is not available.
abandon
Abandon the current stack.
Takes no additional arguments beyond common ones.
view
Inspect the current stack
Takes no additional arguments beyond common ones.
config
Set a configuration value in the config file.
Arguments:
setting (required): Configuration setting in format <section>.<key>=<value>
Examples:
stack-pr config common.verbose=True
stack-pr config common.show_tips=False
stack-pr config repo.target=master
stack-pr config repo.reviewer=user1,user2
stack-pr config repo.branch_name_template=$USERNAME/stack
stack-pr config land.style=disable
stack-pr config land.style=bottom-only
The config command modifies the config file (the .stack-pr.cfg file in the repo root by default, or the path specified by STACKPR_CONFIG environment variable). If the file doesn't exist, it will be created. If a setting already exists, it will be updated.
Config files
Default values for command line options can be specified via a config file.
Path to the config file can be specified via STACKPR_CONFIG envvar, and by
default it's assumed to be .stack-pr.cfg in the current folder.
An example of a config file:
[common]
verbose=True
hyperlinks=True
draft=False
keep_body=False
stash=False
show_tips=True
[repo]
remote=origin
target=main
reviewer=GithubHandle1,GithubHandle2
branch_name_template=$USERNAME/$BRANCH
[land]
style=bottom-only