A CLI tool for managing stacked Git branches with automated rebasing and GitHub PR management. Inspired by Graphite.
Branch stacking is a workflow where you create a series of dependent branches, each building on the previous one. This allows you to:
- Break large features into smaller, reviewable PRs
- Get early feedback on foundational changes while continuing to build on top
- Keep your PRs focused and easy to review
main
└── feature-auth PR #1: Add authentication
└── feature-auth-ui PR #2: Add login UI (depends on #1)
└── feature-tests PR #3: Add tests (depends on #2)
macOS/Linux (curl):
curl -fsSL https://raw.githubusercontent.com/nicomalacho/stack-branch/main/scripts/install.sh | bashmacOS (Homebrew):
brew tap nicomalacho/tap
brew install gstackDirect Download: Download the latest release for your platform from GitHub Releases:
gs-macos-arm64- macOS (Apple Silicon, Intel via Rosetta)gs-linux-x86_64- Linuxgs-windows-x86_64.exe- Windows
# Example: macOS Apple Silicon
curl -L https://github.com/nicomalacho/stack-branch/releases/latest/download/gs-macos-arm64 -o /usr/local/bin/gs
chmod +x /usr/local/bin/gsgit clone https://github.com/nicomalacho/stack-branch.git
cd stack-branch
pip install -e ".[dev]"After installation, use the gs command:
gs --helpGit pass-through: Any command not recognized by gstack is automatically passed to git, so you can use gs as a drop-in replacement for git:
gs status # Same as: git status
gs add . # Same as: git add .
gs commit -m "message" # Same as: git commit -m "message"
gs diff # Same as: git diff
gs log --oneline # Same as: git log --oneline# Initialize gstack in your repository
cd your-repo
gs init
# Create your first stacked branch (works with uncommitted changes!)
gs create feature-auth
# Make changes and commit using gs (passes through to git)
gs add .
gs commit -m "Add authentication module"
# Push just this branch and create a PR
gs push
# Create another branch stacked on top
gs create feature-auth-ui
# Make more changes and commit
gs add .
gs commit -m "Add login UI"
# Push just this branch
gs push
# Or push all branches in the stack at once
gs submitHere's a real-world example of using gstack to implement a feature in multiple PRs:
cd my-project
gs init
# Output: Initialized gstack with trunk branch 'main'.gs create add-user-model
# Output: Created branch 'add-user-model' on top of 'main'.Make your changes:
# Create user model
echo "class User: pass" > user.py
git add user.py
git commit -m "Add User model"gs push
# Output: Pushed and created PR: https://github.com/org/repo/pull/1gs create add-user-api
# Output: Created branch 'add-user-api' on top of 'add-user-model'.Make more changes:
# Add API endpoint
echo "def get_user(): pass" > api.py
git add api.py
git commit -m "Add user API endpoint"gs push
# Output: Pushed and created PR: https://github.com/org/repo/pull/2The PR is automatically created with add-user-model as the base branch.
gs log
# Output:
# Trunk: main
# * add-user-model (https://github.com/org/repo/pull/1)
# add-user-api (https://github.com/org/repo/pull/2)The * indicates your current branch.
If you need to make changes to add-user-model:
git checkout add-user-model
# Make changes
git add .
git commit -m "Update User model"
gs push # Push just this branchAfter pushing changes to add-user-model, sync the dependent branches:
git checkout add-user-api
gs sync
# Output:
# Synced 2 branch(es):
# - add-user-model
# - add-user-api
gs push # Push the rebased branchIf conflicts occur during sync:
gs sync
# Output:
# Conflict in branch 'add-user-model'.
# Resolve the conflicts, stage the files, then run 'gs continue'.
# Or run 'gs abort' to cancel the sync.Resolve conflicts:
# Fix conflicts in your editor
git add <resolved-files>
gs continue
# Output:
# Synced 2 branch(es):
# - add-user-model
# - add-user-api
# Auto-submitted changes.Changes are automatically pushed after successful conflict resolution.
Or abort:
gs abort
# Output: Sync aborted.When you run gs sync, gstack automatically detects merged branches and offers to clean them up:
gs sync
# Output:
# The following branches have been merged:
# - add-user-model
#
# Delete merged branch 'add-user-model'? [Y/n]: y
# Deleted 'add-user-model'
#
# Synced 1 branch(es):
# - add-user-apiChild branches are automatically reparented to the deleted branch's parent.
When you run gs submit or gs push, gstack automatically adds a mermaid diagram comment to your PRs showing the stack structure:
graph TD
main[main]
add-user-model[add-user-model #1]
main --> add-user-model
add-user-api[add-user-api #2]
add-user-model --> add-user-api
This helps reviewers understand how the PR fits into the larger feature.
Running gs sync automatically checks for merged PRs and prompts you to delete the local branches, keeping your stack clean.
When running gs sync, gstack automatically squashes multiple commits on each branch into a single commit before rebasing. This:
- Reduces the number of potential merge conflicts
- Keeps your Git history clean
- Preserves the first commit message
If a branch only has one commit, it remains unchanged.
After successfully resolving conflicts with gs continue, gstack automatically runs gs submit to push your changes. This saves you an extra step in the conflict resolution workflow:
# After resolving conflicts
git add <resolved-files>
gs continue
# Output:
# Synced 2 branch(es):
# - feature
# - feature-ui
# Auto-submitted changes.When you run gs submit, gstack automatically syncs (rebases) all branches first to ensure they're up to date with their parents. If there are conflicts, submit will fail and prompt you to resolve them first.
Any unrecognized command is passed directly to git, so you can use gs as your primary git interface:
gs status # git status
gs add . # git add .
gs add -p # git add -p (interactive mode works!)
gs commit -m "msg" # git commit -m "msg"
gs diff # git diff
gs stash # git stash
gs rebase -i HEAD~3 # git rebase -i HEAD~3This means you only need to type gs instead of git for all your version control needs. Interactive commands like gs add -p (patch mode) and gs rebase -i work correctly because gstack preserves the terminal for interactive git commands.
Unlike some stacking tools, gs create works even when you have uncommitted changes in your working directory. This enables a natural workflow:
# Start working on a feature
echo "new code" > feature.py
# Decide to put it on a new branch
gs create my-feature
# Now commit it
gs add .
gs commit -m "Add new feature"Initialize gstack in the current repository.
gs init # Auto-detect trunk (main or master)
gs init --trunk develop # Specify trunk branch
gs init --force # Reinitialize (clears existing config)Create a new stacked branch.
gs create feature # Stack on current branch
gs create feature --parent main # Stack on specific branchPush the current branch and create/update its PR.
gs push- Pushes only the current branch (with force-with-lease)
- Creates PR if none exists
- Updates PR base if it doesn't match the parent
- Adds stack diagram comment to the PR
Use case: Quick iteration on a single branch without affecting the rest of the stack.
Push all branches in the stack and create/update their PRs.
gs submit- Automatically syncs (rebases) all branches first
- Squashes multiple commits per branch into one
- Pushes all branches in the current stack
- Creates PRs for branches without one (with stack description)
- Updates PR base branches if they don't match the parent
- Adds stack diagram comments to all PRs
Use case: Push the entire stack after making changes across multiple branches.
Rebase the current stack onto the latest trunk.
gs sync- Detects merged branches and offers to delete them
- Auto-squashes multiple commits per branch before rebasing
- Syncs all branches from the current branch's stack (ancestors and descendants)
- Preserves state on conflict for
gs continue
Continue a sync operation after resolving conflicts.
# After resolving conflicts and staging files
gs continue- Continues the rebase from where it stopped
- Auto-submits changes after successful completion
Abort the current sync operation.
gs abortShow the current stack structure.
gs logDelete a branch from the stack.
gs delete feature # Delete branch
gs delete feature --force # Force delete unmerged branchChild branches are automatically reparented to the deleted branch's parent.
- Git
- Python 3.9+
- GitHub CLI (
gh) for push/submit functionality
gstack stores its configuration in .git/.gstack_config.json. This file tracks:
- Trunk branch name
- Tracked branches and their parent relationships
- PR URLs
Since the config is stored inside the .git/ directory, it is automatically ignored by git and does not need to be added to .gitignore. Note that this means the stack configuration is local to each clone of the repository.
MIT