Stacked PRs for GitHub.
ez is a fast, lightweight CLI for managing stacked pull requests on GitHub. It shells out to git and gh so there's nothing magical happening under the hood — just the tools you already know, orchestrated intelligently.
Large pull requests are hard to review. Stacked PRs let you break work into a chain of small, focused branches where each branch builds on the one below it:
main
└── feat/auth-types ← PR #1 (data models)
└── feat/auth-api ← PR #2 (API routes, depends on #1)
└── feat/auth-ui ← PR #3 (frontend, depends on #2)
Reviewers see small diffs. You keep working without waiting. When PR #1 merges, ez rebases the rest of the stack automatically.
The problem is that git doesn't know about stacks. Rebasing, reordering, and keeping GitHub PRs pointed at the right base branch is tedious and error-prone. ez handles all of that for you.
# Install
cargo install ez-stack
# Initialize in any git repo
cd your-repo
ez init
# Start building a stack
ez create feat/parse-config
# ... make changes ...
ez commit -m "add config parser"
ez create feat/use-config
# ... make changes ...
ez commit -m "wire config into app"
# Push and open PRs for the whole stack
ez push
ez submitThat's it. Two PRs, correctly chained, with GitHub base branches set automatically.
| Command | Description |
|---|---|
ez init |
Initialize ez in the current repository |
ez create <name> |
Create a new branch on top of the current stack |
ez commit [-m <msg>] |
Commit staged changes to the current branch |
ez amend |
Amend the last commit on the current branch |
ez delete [<name>] |
Delete a branch from the stack and restack |
ez move <--up|--down> |
Reorder the current branch within the stack |
| Command | Description |
|---|---|
ez sync |
Fetch main, rebase the entire stack, clean up merged branches |
ez restack |
Rebase each branch in the stack onto its parent |
ez push |
Force-push all branches in the stack to the remote |
| Command | Description |
|---|---|
ez up |
Check out the branch above the current one |
ez down |
Check out the branch below the current one |
ez top |
Check out the top of the stack |
ez bottom |
Check out the bottom of the stack |
ez checkout <name> |
Check out any branch in the stack by name |
| Command | Description |
|---|---|
ez submit |
Create or update GitHub PRs for all branches in the stack |
ez merge |
Merge the bottom PR and restack |
| Command | Description |
|---|---|
ez log |
Show the full stack with branch names, commit counts, and PR status |
ez status |
Show the current branch and its position in the stack |
Here's a complete session building a three-branch stack:
# 1. Start from main
git checkout main && git pull
ez init
# 2. Create the first branch in the stack
ez create feat/auth-types
cat > src/auth/types.rs << 'EOF'
pub struct User { pub id: u64, pub email: String }
pub struct Session { pub token: String, pub user_id: u64 }
EOF
ez commit -m "define User and Session types"
# 3. Stack a second branch on top
ez create feat/auth-api
cat > src/auth/api.rs << 'EOF'
pub fn login(email: &str) -> Session { /* ... */ }
pub fn logout(session: &Session) { /* ... */ }
EOF
ez commit -m "add login/logout API"
# 4. Stack a third branch on top
ez create feat/auth-middleware
cat > src/middleware/auth.rs << 'EOF'
pub fn require_auth(req: &Request) -> Result<User, AuthError> { /* ... */ }
EOF
ez commit -m "add auth middleware"
# 5. See the full stack
ez log
# main
# ├── feat/auth-types (1 commit)
# │ ├── feat/auth-api (1 commit)
# │ │ ├── feat/auth-middleware (1 commit) ← you are here
# 6. Push everything and open PRs
ez push
ez submit
# Creates 3 PRs:
# feat/auth-types → main
# feat/auth-api → feat/auth-types
# feat/auth-middleware → feat/auth-api
# 7. After feat/auth-types is reviewed and merged on GitHub:
ez sync
# Fetches main (which now includes auth-types),
# rebases auth-api onto main, rebases auth-middleware onto auth-api,
# deletes the merged feat/auth-types branch,
# and updates PR base branches on GitHub.ez is intentionally simple in its architecture:
- No custom git internals. Every git operation is a call to the
gitCLI. Every GitHub operation goes throughgh. You can always see exactly what happened by reading your git log. - Stack metadata is stored in
.git/ez/stack.json— a single JSON file tracking branch order, parent relationships, and associated PR numbers. It's local to your repo and ignored by git. - Restacking uses
git rebase --ontoto move each branch in the stack onto its updated parent. This is the same operation you'd do by hand;ezjust does it for every branch in the right order. - PR management calls
gh pr createandgh pr editto set base branches so GitHub shows the correct, minimal diff for each PR in the stack.
{
"version": 1,
"trunk": "main",
"branches": [
{ "name": "feat/auth-types", "parent": "main", "pr": 101 },
{ "name": "feat/auth-api", "parent": "feat/auth-types", "pr": 102 },
{ "name": "feat/auth-middleware", "parent": "feat/auth-api", "pr": null }
]
}- git 2.38+
- gh (GitHub CLI), authenticated via
gh auth login - A GitHub repository with push access
cargo install ez-stackgit clone https://github.com/rohoswagger/ez-stack.git
cd ez-stack
cargo install --path .curl -fsSL https://raw.githubusercontent.com/rohoswagger/ez-stack/main/install.sh | bashTo install a specific version:
curl -fsSL https://raw.githubusercontent.com/rohoswagger/ez-stack/main/install.sh | bash -s -- v0.1.0Pre-built binaries for Linux and macOS are available on the Releases page.
Contributions are welcome! Please read CONTRIBUTING.md for development setup, code style, and how to submit changes.
MIT. See LICENSE for details.