Skip to main content

Stacked PRs for GitHub — agent-first CLI for parallel development

Project description

ez

Stacked PRs for GitHub.

License: MIT Rust CI


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.

Why stacked PRs?

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.

Quick start

# Install
pip install ez-stack
ez setup --yes

# 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 the entire stack and open PRs for all branches
ez submit

That's it. Two PRs, correctly chained, with GitHub base branches set automatically.

Using ez with AI Agents

ez is designed to work well in automated and agentic contexts:

  • JSON outputez status --json and ez log --json emit machine-readable data that agents can parse without screen-scraping.
  • Non-interactive flagsez checkout <name> or ez checkout 42 bypasses the TUI picker. ez commit --if-changed exits cleanly when there's nothing to commit.
  • Autostashez sync --autostash eliminates the git stash && ... && git stash pop dance.
  • Structured exit codes — each failure mode has a distinct exit code (rebase conflict = 3, stale remote = 4, usage error = 5, unstaged changes = 6) so agents can take the right recovery action.

Install the ez skill

If your agent supports Skills, install the repo's ez-workflow skill directly from GitHub:

npx skills add https://github.com/rohoswagger/ez-stack --skill ez-workflow

That installs the instructions from SKILL.md, which teaches the agent when to use ez instead of raw git/gh commands in an ez-managed repo.

Quick install check

pip install ez-stack && ez --version

Key agent patterns

# Parse current branch state
ez status --json
# → {"branch":"feat/x","parent":"main","pr_number":42,"children":[],"needs_restack":false,...}

# Only commit if there are staged changes
ez commit -m "chore: format" --if-changed

# Sync without a manual stash dance
ez sync --autostash

# Get just the PR URL for scripting
open $(ez pr-link)

# Find branches that need restacking
ez log --json | jq '.[] | select(.needs_restack)'

For agents that support local instruction files, AGENTS.md contains a compact install-and-usage guide. For skills-based agents, install ez-workflow with npx skills add and use that skill directly.

Agent setup prompt

Copy-paste this into any AI coding agent to get ez installed and configured:

Install ez-stack, a CLI for stacked PRs on GitHub, and set it up in this repo.

Steps:

  1. Install: pip install ez-stack (or update: ez update)
  2. Configure shell: ez setup --yes
  3. Initialize in this repo: ez init
  4. Install the skill: ez skill install (agents in this repo auto-discover it)
  5. Add these rules to the project's CLAUDE.md (or equivalent agent instructions file):
## ez-stack

This repo uses `ez` for stacked PRs. When `.git/ez/stack.json` exists, use `ez` instead of raw git/gh commands:

- `ez create <name>` instead of `git checkout -b`
- `ez commit -m "msg"` instead of `git commit` (auto-restacks children, shows diff stat)
- `ez commit -m "msg" -- path1 path2` to stage specific files
- `ez diff --stat` instead of `git diff main...HEAD --stat`
- `ez parent` instead of parsing `ez status` for the parent branch
- `ez push` instead of `git push` / `gh pr create`
- `ez submit` to push the entire stack
- `ez sync --autostash` to sync with trunk

Key flags: `--json` on status/log for machine output, `--if-changed` on commit to no-op when nothing staged, `--from <base>` on create to skip checkout.

Output: every command appends `[ok | 45ms]` or `[exit:3 | 120ms]` to stderr.
Exit codes: 0=ok, 2=gh error, 3=conflict, 4=stale ref, 5=usage error, 6=unstaged changes.

Commands

Stack creation & editing

Command Description
ez init Initialize ez in the current repository
ez create <name> Create a new branch stacked on the current branch
ez create <name> -m "msg" Create branch and commit staged changes in one step
ez create <name> -am "msg" Create branch, stage all tracked changes, and commit
ez create <name> --from <base> Create branch from a specific base without checking it out first
ez commit -m <msg> Commit staged changes, restack children, show diff stat
ez commit -m "subj" -m "body" Multi-paragraph commit (repeated -m, like git)
ez commit -m <msg> -- <paths> Stage specific paths and commit
ez commit -m <msg> --if-changed Commit only if there are staged changes (no-op otherwise)
ez amend Amend the last commit and restack children
ez delete [<name>] Delete a branch from the stack and restack
ez move --onto <branch> Reparent the current branch onto another branch

Syncing & rebasing

Command Description
ez sync Fetch trunk, detect merged PRs, clean up, and restack
ez sync --dry-run Preview what sync would do without making changes
ez sync --autostash Stash uncommitted changes before sync, restore after
ez restack Rebase each branch onto its parent

Navigation

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 Interactively select a branch to check out
ez checkout <name> Switch directly to a branch by name (non-interactive)
ez checkout <number> Switch directly to a branch by PR number (non-interactive)

GitHub integration

Command Description
ez push Push current branch only and create/update its PR
ez push --title "..." --body "..." Push and set/update PR title and body
ez push --base <branch> Push and override the PR base branch
ez submit Push all branches in the stack and create/update all PRs
ez pr Open the current branch's PR in the browser
ez pr-link Print the PR URL to stdout (pipeable)
ez pr-edit Edit the PR body in $EDITOR
ez pr-edit --title "..." --body "..." Edit the PR title/body directly
ez draft Mark the current PR as a draft
ez ready Mark the current PR as ready for review
ez merge Merge the bottom PR of the stack via GitHub

Inspection & diffing

Command Description
ez log Show the full stack with branch names, commit counts, and PR status
ez log --json Show the full stack as a JSON array (machine-readable)
ez status Show the current branch and its position in the stack
ez status --json Show current branch info as JSON (machine-readable)
ez diff Show diff of current branch vs parent (what the PR reviewer sees)
ez diff --stat Show only the diffstat summary
ez diff --name-only Show only changed file names
ez parent Print the parent branch name to stdout (pipeable)
ez branch List all branches with PR numbers and worktree paths

Updating

Command Description
ez update Update ez to the latest version
ez update --check Check for updates without installing
ez update --version v0.1.12 Install a specific version

ez push vs ez submit

ez push ez submit
Scope Current branch only All branches from trunk to current
PRs Creates/updates PR for current branch Creates/updates PRs for all branches
When to use Iterating on a single branch First push of a stack, or after restacking all branches

Note: Running gh pr create after ez push will fail — ez push already created the PR.

Example workflow

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 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 create with a commit message

ez create accepts -m to commit staged changes in one step:

git add src/auth.rs
ez create feat/auth -m "add auth module"
# equivalent to: ez create feat/auth && ez commit -m "add auth module"

Use -a to stage all tracked changes automatically (like git commit -a):

ez create feat/auth -am "add auth module"
# equivalent to: git add -A && ez create feat/auth -m "add auth module"

How it works

ez is intentionally simple in its architecture:

  • No custom git internals. Every git operation is a call to the git CLI. Every GitHub operation goes through gh. 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 --onto to move each branch in the stack onto its updated parent. This is the same operation you'd do by hand; ez just does it for every branch in the right order.
  • PR management calls gh pr create and gh pr edit to set base branches so GitHub shows the correct, minimal diff for each PR in the stack.

Stack metadata format

{
  "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 }
  ]
}

Prerequisites

  • git 2.38+
  • gh (GitHub CLI), authenticated via gh auth login
  • A GitHub repository with push access

Installation

pip install ez-stack
ez setup --yes
ez init

That's it. pip install gives you the ez binary. ez setup configures your shell for auto-cd on worktree create/delete/switch. ez init initializes stacking in your repo.

Other install methods

Pre-built binaries are also available on the Releases page, or via the install script:

curl -fsSL https://raw.githubusercontent.com/rohoswagger/ez-stack/main/install.sh | bash

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for development setup, code style, and how to submit changes.

License

MIT. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

ez_stack-0.2.11-py3-none-manylinux_2_17_x86_64.whl (727.0 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

ez_stack-0.2.11-py3-none-manylinux_2_17_aarch64.whl (666.8 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

ez_stack-0.2.11-py3-none-macosx_11_0_arm64.whl (648.8 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

ez_stack-0.2.11-py3-none-macosx_10_12_x86_64.whl (696.4 kB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file ez_stack-0.2.11-py3-none-manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for ez_stack-0.2.11-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 cdbcd70c93062f6028a6e6ad9fd2102cbf4c286b42f30d0f4a3dc0bc4be7a5ff
MD5 5b1d2919625f277718c0925e92cdaaf6
BLAKE2b-256 d21e4da84c6f8a2f73d2971d93ff785cc9fccdcd8f1ea33dafe0364d9c4a6436

See more details on using hashes here.

Provenance

The following attestation bundles were made for ez_stack-0.2.11-py3-none-manylinux_2_17_x86_64.whl:

Publisher: python-wheel.yml on rohoswagger/ez-stack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ez_stack-0.2.11-py3-none-manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for ez_stack-0.2.11-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 8da526fd883ed5504fce35af99e24734dccdd9ea62d5f95155f7314ebd1b43f7
MD5 27fe0f7a7275eb36a61dd4a462dbc466
BLAKE2b-256 36f1b5d685366545d538f54be1e3d26831618dd72dbf8967de396958e7f75ce3

See more details on using hashes here.

Provenance

The following attestation bundles were made for ez_stack-0.2.11-py3-none-manylinux_2_17_aarch64.whl:

Publisher: python-wheel.yml on rohoswagger/ez-stack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ez_stack-0.2.11-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for ez_stack-0.2.11-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6c1303cbd37c37159ddbb607a741685c319fc8612883fb3582a194042e274472
MD5 7bdb55d264254aaa72a9b6404a11c675
BLAKE2b-256 cf524399a5f36c0f3f1a524cb1e021be9f9283fb722bee13396c62f22451a787

See more details on using hashes here.

Provenance

The following attestation bundles were made for ez_stack-0.2.11-py3-none-macosx_11_0_arm64.whl:

Publisher: python-wheel.yml on rohoswagger/ez-stack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ez_stack-0.2.11-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for ez_stack-0.2.11-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 e84d7e2c86419473542de01178db4f7724ed5945687b23e45a028a81853b4041
MD5 c44d317bc5ae58d0269bc77878ce607f
BLAKE2b-256 1265ad8a996f3d4afcfda88aec614c5ff1ed734ea5ca13ea63729e3200b86dd6

See more details on using hashes here.

Provenance

The following attestation bundles were made for ez_stack-0.2.11-py3-none-macosx_10_12_x86_64.whl:

Publisher: python-wheel.yml on rohoswagger/ez-stack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page