A lightweight, single-file CLI tool for managing stacked Git branches. git-stack helps you work with dependent branches without requiring cloud services or complex setups - just Python 3 and Git.
Stacked branches (also called "stacked diffs" or "stacked PRs") is a development workflow where you create a chain of dependent feature branches, each building on top of the previous one. This allows you to:
- Break large features into smaller, reviewable chunks
- Get faster code reviews (smaller PRs = quicker reviews)
- Continue working while waiting for reviews
- Maintain a clean commit history
Traditional:
main β feature-branch (big PR with 20 files changed)
Stacked:
main β auth-foundation β auth-ui β auth-testing
(3 files) (5 files) (2 files)
# Clone or download stack.py
curl -o stack.py https://raw.githubusercontent.com/your-repo/stack-cli/main/stack.py
# Run the installer
chmod +x install.sh
./install.sh
# Or install manually
mkdir -p ~/.local/bin
cp stack.py ~/.local/bin/stack
chmod +x ~/.local/bin/stack
# Make sure ~/.local/bin is in your PATH
export PATH="$HOME/.local/bin:$PATH"
Verify installation:
git-stack --help
# Start from main
git checkout main
# Create your first branch
echo "Authentication module" > auth.py
git add auth.py
git-stack create auth-foundation -m "Add auth foundation"
# Stack another branch on top
echo "Login UI component" > login.py
git add login.py
git-stack create auth-ui -m "Add login UI"
# Stack a third branch
echo "Auth tests" > auth_test.py
git add auth_test.py
git-stack create auth-tests -m "Add auth tests"
# View your stack
git-stack tree
Output:
π Stack tree (base: main)
β main
ββ auth-foundation
ββ auth-ui
ββ auth-tests (current)
# Create a new branch with a commit (requires staged files)
git add <files>
git-stack create <branch-name> -m "Commit message"
# Create a branch without committing (keeps changes staged)
git add <files>
git-stack create <branch-name> --no-commit
# Note: You must stage files before creating a branch
# git-stack create will fail if no files are staged
# Move to parent branch
git-stack up
# Move to child branch (interactive if multiple children)
git-stack down
# Jump to the top of the stack
git-stack top
# Jump to the bottom of the stack (first branch above main)
git-stack bottom
# Interactive branch checkout
git-stack checkout
# Show the branch tree
git-stack tree
# Show current branch info
git-stack status
# Amend the current commit and restack children
git add <modified-files>
git-stack modify
# The modify command automatically rebases all child branches
# Pull latest main and restack all branches
git-stack sync
# Force reset main to origin/main (with safety checks)
git-stack sync --force
# Restack current branch and its children
git-stack restack
# Restack a specific branch
git-stack restack <branch-name>
# Push branches and create/update PRs for the entire stack
git-stack submit
# Submit a specific branch and its ancestors
git-stack submit <branch-name>
# Requires GitHub CLI (gh) to be installed and authenticated
# Install: https://cli.github.com/
You're building a user profile feature with backend, frontend, and tests.
# Start from main
git checkout main
# Layer 1: Database models
git add models/user_profile.py
git-stack create profile-models -m "Add user profile database models"
# Layer 2: API endpoints (builds on models)
git add api/profile_endpoints.py
git-stack create profile-api -m "Add profile API endpoints"
# Layer 3: Frontend components (builds on API)
git add components/ProfilePage.tsx
git-stack create profile-ui -m "Add profile UI components"
# Layer 4: Tests (builds on everything)
git add tests/test_profile.py
git-stack create profile-tests -m "Add comprehensive profile tests"
# View your stack
git-stack tree
Output:
π Stack tree (base: main)
β main
ββ profile-models
ββ profile-api
ββ profile-ui
ββ profile-tests (current)
Creating Pull Requests:
# Automated approach (requires GitHub CLI)
git-stack submit
# Output:
# π€ Submitting stack (4 branch(es)):
# profile-models β main
# profile-api β profile-models
# profile-ui β profile-api
# profile-tests β profile-ui
#
# [1/4] Processing profile-models...
# Pushing profile-models to origin...
# Creating PR: profile-models β main
# β Created PR
# π https://github.com/user/repo/pull/123
# ...
# Manual approach
git push -u origin profile-models
git push -u origin profile-api
git push -u origin profile-ui
git push -u origin profile-tests
# Create PRs in GitHub
# PR 1: profile-models -> main
# PR 2: profile-api -> profile-models
# PR 3: profile-ui -> profile-api
# PR 4: profile-tests -> profile-ui
Your reviewer asks for changes in the middle of your stack.
# You're on profile-tests, reviewer comments on profile-api
git-stack checkout profile-api
# Make the requested changes
git add api/profile_endpoints.py
# Amend the commit and automatically restack all children
git-stack modify
# All child branches (profile-ui, profile-tests) are automatically rebased!
What happens:
profile-api
commit is amended with your changesprofile-ui
is automatically rebased onto the updatedprofile-api
profile-tests
is automatically rebased onto the updatedprofile-ui
Someone merged a PR to main while you're working on your stack.
# Sync your stack with the latest main
git-stack sync
# This will:
# 1. Pull latest main from origin
# 2. Rebase profile-models onto main
# 3. Rebase profile-api onto profile-models
# 4. Rebase profile-ui onto profile-api
# 5. Rebase profile-tests onto profile-ui
# Sync encounters a conflict
git-stack sync
# Output:
# β οΈ Conflict detected while restacking profile-api
# Please resolve conflicts, then run: git-stack continue
# Resolve conflicts manually
git add <resolved-files>
# Continue the restack process
git-stack continue
# Stack automatically restacks remaining children
# Stack 1: Authentication feature
git checkout main
git add auth.py
git-stack create auth-base -m "Add auth base"
git add login.py
git-stack create auth-login -m "Add login"
# Stack 2: Separate feature - User settings
git checkout main
git add settings.py
git-stack create settings-base -m "Add settings base"
git add preferences.py
git-stack create settings-prefs -m "Add preferences"
# View both stacks
git-stack tree
Output:
π Stack tree (base: main)
β main
ββ auth-base
β ββ auth-login
ββ settings-base
ββ settings-prefs (current)
# You're deep in a stack and want to go back to the beginning
git-stack bottom # Jump to first branch in stack
# Navigate up the tree
git-stack up # Go to parent branch
# Check where you are
git-stack status
# Current branch: auth-login
# Parent: auth-base
# Children: auth-tests, auth-docs
# If multiple children exist, stack prompts you to choose
git-stack down
# Multiple children available:
# 1. auth-tests
# 2. auth-docs
# Select branch number (or 'q' to quit): 1
Useful when you want to create a branch structure first, then commit later.
# Stage some changes
git add feature.py
# Create branch without committing
git-stack create my-feature --no-commit
# Changes remain staged
git status
# On branch my-feature
# Changes to be committed:
# new file: feature.py
# Commit when ready
git commit -m "Add feature"
# Stack maintains automatic backups
git-stack status
# Output:
# Current branch: feature-branch
# Parent: main
# Children: none
#
# πΎ Backup available (created 5 minutes ago)
# Restore with: git-stack restore-backup
# If something goes wrong
git-stack restore-backup
# Type 'yes' to restore from backup, or anything else to cancel: yes
# β Metadata restored from backup
You have a large feature branch with 50+ file changes. Split it into reviewable chunks:
# On your large feature branch
git checkout my-large-feature
# Create a clean stack from main
git checkout main
# Cherry-pick related commits into separate branches
git checkout main
git checkout -b feature-part1
git cherry-pick <commit-hash-1> <commit-hash-2>
# Now track it with stack (from main)
git checkout main
git add . # Add any uncommitted changes
git-stack create feature-part1 -m "Part 1: Core functionality"
# Continue with next part
git cherry-pick <commit-hash-3> <commit-hash-4>
git-stack create feature-part2 -m "Part 2: UI components"
# And so on...
# Check all your stacks
git-stack tree
# Regularly sync to stay updated
git-stack sync
# Push all branches for PR review
git push --all origin
Each branch should represent one logical change:
β
Good: git-stack create add-user-validation -m "Add email validation"
β Bad: git-stack create misc-changes -m "Fix stuff"
β
Good: auth-oauth-integration
β
Good: ui-dark-mode-toggle
β Bad: feature-1
β Bad: fix
# Make small commits within a branch
git commit -m "Add basic structure"
git commit -m "Add error handling"
git commit -m "Add tests"
# Stack when you have a complete, reviewable unit
git-stack create auth-middleware -m "Add authentication middleware"
# Sync at least once a day
git-stack sync
# Before starting new work
git checkout main
git pull
git-stack sync
# When conflicts occur during sync
git-stack sync
# β οΈ Conflict detected...
# Don't ignore it - resolve immediately
vim conflicted-file.py
git add conflicted-file.py
git-stack continue
# Try out changes without committing
git add experimental-feature.py
git-stack create experiment --no-commit
# Decide later whether to commit or discard
git commit -m "Keep this change"
# or
git reset HEAD experimental-feature.py
Stack (this tool):
- β Zero dependencies (just Python + Git)
- β Single file, easy to audit
- β No cloud service required
- β Works offline
- β Local metadata only
- β Optional GitHub integration (via GitHub CLI)
- β
Automated PR creation/updating with
git-stack submit
- β No web UI
Graphite:
- β GitHub PR management
- β Web dashboard
- β Team collaboration features
- β Advanced PR workflows
- β Requires cloud service
- β More complex setup
- β Paid tiers for teams
When to use Stack:
- You want a simple, local-first tool
- You're comfortable with Git and GitHub CLI
- You value simplicity and control
- You want optional PR automation without cloud dependencies
When to use Graphite:
- You need advanced team collaboration features
- You want a web UI for visualization
- You need enterprise features
# You created a branch outside of stack
git checkout -b manual-branch
# You'll need to recreate it using stack to track it
git checkout main
git cherry-pick <commits-from-manual-branch>
git-stack create manual-branch -m "Track existing branch"
# Metadata is corrupted
# Check .git/stack-metadata.json
# Restore from backup
git-stack restore-backup
# Check if you're in a rebase
git status
# If in a rebase
git add <resolved-files>
git-stack continue
# Or abort if needed
git rebase --abort
# Stack auto-cleans deleted branches
git-stack tree
# π§Ή Cleaned up 2 deleted branch(es): old-branch-1, old-branch-2
Command | Description |
---|---|
git-stack create <name> -m "msg" |
Create a new branch on top of current |
git-stack create <name> --no-commit |
Create branch without committing |
git-stack checkout [name] |
Checkout a branch (interactive if no name) |
git-stack tree |
Display the branch tree |
git-stack status |
Show current branch status |
git-stack up |
Move to parent branch |
git-stack down |
Move to child branch |
git-stack top |
Jump to top of current stack |
git-stack bottom |
Jump to bottom of current stack |
git-stack modify |
Amend current commit and restack children |
git-stack sync |
Pull main and restack all branches |
git-stack sync --force |
Force reset main to origin/main |
git-stack restack [name] |
Restack branch and its children |
git-stack continue |
Continue after resolving conflicts |
git-stack restore-backup |
Restore metadata from backup |
git-stack submit [branch] |
Push branches and create/update PRs for the stack |
# Add to ~/.bashrc or ~/.zshrc
alias sk='git-stack'
# Now you can use:
sk tree
sk create my-branch -m "message"
sk co # Interactive checkout
sk sync
# Or use git's subcommand style:
git stack tree
# Add to ~/.bashrc or ~/.zshrc
alias stree='git-stack tree'
alias sstatus='git-stack status'
alias sup='git-stack up'
alias sdown='git-stack down'
alias ssync='git-stack sync'
# Automated approach - use git-stack submit
git-stack submit
# Or create PRs manually for specific branches
gh pr create --base parent-branch --head feature-branch --fill
# Always check before pushing
git-stack tree
# Push all branches
git push --all origin
# Never work directly on main
git checkout main
# Always create a branch first
git-stack create <feature-name> -m "Start feature"
Stack is a single-file tool designed to be simple and auditable. Contributions are welcome!
- Bug reports: Open an issue with reproduction steps
- Feature requests: Describe your use case
- Pull requests: Keep changes focused and tested
Run the test suite:
./test-stack.sh
MIT License - See LICENSE file for details
Inspired by Graphite CLI, designed for simplicity and local-first development.