<div align="center">
<h1>stax</h1>
<p>
<strong>A modern CLI for stacked Git branches and PRs.</strong>
</p>
<p>
<img alt="Version" src="https://img.shields.io/badge/version-0.1.0-blue">
<a href="https://github.com/cesarferreira/stax/actions/workflows/rust-tests.yml">
<img alt="CI" src="https://github.com/cesarferreira/stax/actions/workflows/rust-tests.yml/badge.svg">
</a>
<img alt="Performance" src="https://img.shields.io/badge/~21ms-startup-brightgreen">
<img alt="Git" src="https://img.shields.io/badge/git-git2-f34f29">
<img alt="Async" src="https://img.shields.io/badge/async-tokio-2f74c0">
<img alt="License" src="https://img.shields.io/badge/license-MIT-green">
</p>
<p>
<img alt="stax status screenshot" src="assets/screenshot.png">
<p>
Built in Rust for speed, inspired by <a href="https://github.com/bradymadden97/freephite">freephite</a> but reimagined with a cleaner UX and new features.
</p>
</p>
</div>
## Why stax?
- **Fast** - Native Rust binary with libgit2, runs in ~21ms
- **Modern UX** - Clear error messages with actionable suggestions
- **Visual stack view** - Beautiful tree rendering with colors and PR status
- **Flexible** - Force flags, detailed logs, and smart defaults
- **Compatible** - Uses same metadata format as freephite (migrate instantly)
## Install
### Homebrew (macOS/Linux)
```bash
brew tap cesarferreira/tap
brew install stax
```
### Cargo binstall (prebuilt binaries)
```bash
cargo binstall stax
```
### Cargo (build from source)
```bash
cargo install --git https://github.com/cesarferreira/stax
```
### From source
```bash
git clone https://github.com/cesarferreira/stax
cd stax
cargo install --path .
```
## Quick Start
```bash
# Authenticate with GitHub (for PR creation)
stax auth
# Create your first stacked branch
stax bc feat/my-feature
# Make changes, commit, then create another branch on top
stax bc feat/another-feature
# View your stack
stax s
# Submit all branches as PRs
stax ss
# When parent branch changes, sync and restack
stax rs --restack
```
## Initialization
On first run, stax will initialize the repository by selecting a trunk branch (usually `main` or `master`). In non-interactive mode, it auto-detects the trunk if possible.
## Commands
### Shortcuts (freephite compatible)
| `stax ss` | **S**ubmit **s**tack - push branches and create/update PRs |
| `stax rs` | **R**epo **s**ync - pull trunk, delete merged branches |
| `stax rs --restack` | Repo sync + restack branches |
| `stax bco` | **B**ranch **c**heck**o**ut - interactive branch picker |
| `stax create <name>` | Create a new stacked branch (alias: `c`, `bc`) |
| `stax create -m "msg"` | Create branch, stage all, and commit with message |
| `stax create -a` | Create branch and stage all changes (like `git commit -a`) |
| `stax create -am "msg"` | Create branch, stage all, and commit (like `git commit -am`) |
| `stax m` | **M**odify - stage all changes and amend to current commit |
| `stax t` | Switch to **t**runk branch |
| `stax pr` | Open the current branch's PR in browser |
| `stax u` / `stax bu` | Move **u**p the stack (to child branch) |
| `stax u 2` | Move up 2 branches |
| `stax d` / `stax bd` | Move **d**own the stack (to parent branch) |
| `stax d 3` | Move down 3 branches |
| `stax top` | Move to the tip of the stack |
| `stax bottom` | Move to the base of the stack (above trunk) |
### Full Commands
| `stax status` | `s`, `ls` | Show the current stack (simple view) |
| `stax log` | `l` | Show stack with commits and PR info |
| `stax create` | `c`, `bc` | Create a new stacked branch |
| `stax diff` | | Show diffs for each branch vs parent + aggregate stack diff |
| `stax range-diff` | | Show range-diff for branches that need restack |
| `stax sync` | `rs` | Pull trunk, delete merged branches |
| `stax sync --restack` | | Also restack after syncing |
| `stax restack` | | Rebase current branch onto parent |
| `stax restack --all` | | Restack all branches that need it |
| `stax submit` | `ss` | Push and create/update PRs (interactive prompts) |
| `stax submit --draft` | | Create PRs as drafts |
| `stax submit --no-pr` | | Just push, skip PR creation |
| `stax checkout [branch]` | `co`, `bco` | Checkout a branch (interactive if no arg) |
| `stax trunk` | `t` | Switch to trunk branch |
| `stax up [n]` | `u` | Move up n branches (default: 1) |
| `stax down [n]` | `d` | Move down n branches (default: 1) |
| `stax top` | | Move to the tip of the stack |
| `stax bottom` | | Move to the base of the stack (above trunk) |
| `stax continue` | `cont` | Continue after resolving conflicts |
| `stax modify` | `m` | Stage all changes and amend to current commit |
| `stax pr` | | Open the current branch's PR in browser |
| `stax auth` | | Set GitHub personal access token |
| `stax config` | | Show config file path and contents |
| `stax doctor` | | Check stax configuration and repo health |
### Notable Flags and Behavior
#### Output and scripting
- `stax status --json --compact --stack <branch> --all --quiet`
- `stax log --json --compact --stack <branch> --all --quiet`
- Status/log output includes PR state, ahead/behind counts, and CI status (cached with 5-min TTL).
#### Submit
- Interactive prompts for new PRs: edit title, edit body in `$EDITOR`, choose draft or publish.
- Prefills PR title/body from branch names, commit messages, and PR templates.
- `stax submit --reviewers alice,bob --labels bug --assignees alice --yes --no-prompt`
- Updates a single "stack summary" comment with PR links.
- Prints PR URLs when done; use `stax pr` to open in browser.
#### Sync/Restack
- Detects dirty working tree and offers to stash before restack/sync.
- `stax sync --safe` avoids `reset --hard` when updating trunk.
- `stax sync --verbose` shows detailed git output for debugging.
- `stax sync --continue` and `stax restack --continue` resume after conflicts.
#### Branching and navigation
- `stax create -am "message"` stages all changes and commits, like `git commit -am`.
- `stax create -a` stages all changes without committing (like `git add -A`).
- `stax create --from <branch>` chooses a base branch (defaults to current).
- `stax create --prefix feature -m "auth"` overrides the configured prefix for this branch.
- `stax branch reparent --branch <name> --parent <name>` reattach branches.
- Parent selection is interactive when ambiguous; warnings when parent is missing on remote.
- `stax checkout --trunk`, `--parent`, `--child <n>` quick jumps; picker shows commits/PR info/restack status.
#### Diffs
- `stax diff` shows each branch vs parent plus an aggregate stack diff.
- `stax range-diff` highlights restack effects.
#### Doctor
- `stax doctor` checks repo health, remotes, and provider configuration.
### Branch Commands
| `stax branch create <name>` | `b c` | Create a new stacked branch |
| `stax branch checkout` | `b co` | Interactive branch checkout |
| `stax branch track` | | Track an existing branch |
| `stax branch reparent` | | Change the parent of a tracked branch |
| `stax branch delete` | `b d` | Delete a branch |
| `stax branch fold` | `b f` | Fold current branch into its parent |
| `stax branch squash` | `b sq` | Squash commits on current branch |
| `stax branch up` | `b u` | Move up the stack (to child branch) |
| `stax branch down` | | Move down the stack (to parent branch) |
### Upstack/Downstack
| `stax upstack restack` | `us restack` | Restack all branches above current |
| `stax downstack get` | `ds get` | Show branches below current |
## Example Workflow
```bash
# Start on main
git checkout main
# Create a stacked branch for your feature (stages + commits if -m provided)
stax bc -m "Add auth API"
# Create another branch on top
stax bc -m "Add auth UI"
# Need to make changes? Amend them to the current commit
echo "fix" >> auth.rs
stax m # stages all + amends to current commit
stax m -m "new message" # ...or with a new commit message
# View your stack
stax s
# │ ◉ feat/auth-ui ← you are here
# │ ○ feat/auth-api
# ○─┘ main
# Navigate the stack
stax d # move down to feat/auth-api (or: stax bd)
stax u # move back up to feat/auth-ui (or: stax bu)
stax u 2 # move up 2 branches
stax top # jump to the tip of the stack
stax bottom # jump to the base (first branch above trunk)
stax t # jump to trunk
# Submit all PRs
stax ss
# Submitting 2 branch(es) to owner/repo...
# Pushing feat/auth-api... ✓
# Pushing feat/auth-ui... ✓
# Creating/updating PRs...
# Creating PR for feat/auth-api... ✓ #123
# Creating PR for feat/auth-ui... ✓ #124
# If main gets updated, sync and restack
stax rs --restack
```
## How It Works
stax stores stack metadata in Git refs (`refs/branch-metadata/<branch>`), the same format as freephite. This means:
- No external files or databases
- Metadata travels with your repo
- You can use both `stax` and `fp` on the same repo
Each branch tracks:
- Parent branch name
- Parent branch revision (to detect when restack is needed)
- PR info (number, state)
## Configuration
Config is stored at `~/.config/stax/config.toml`:
```toml
[branch]
prefix = "cesar/" # Auto-prefix new branches (e.g., "my-feature" → "cesar/my-feature")
date = false # Add date to branch names (e.g., "2024-01-15-my-feature")
replacement = "-" # Character to replace spaces and special chars
[remote]
name = "origin" # Remote name to use
provider = "github" # github, gitlab, gitea
base_url = "https://github.com" # Web base URL
api_base_url = "https://api.github.com" # Optional (GitHub Enterprise)
```
View your config with `stax config`.
### GitHub Authentication
GitHub token is stored **separately** from config (not in dotfiles).
**Priority order:**
1. `STAX_GITHUB_TOKEN` env var (highest)
2. `GITHUB_TOKEN` env var
3. `~/.config/stax/.credentials` file (lowest)
```bash
# Option 1: stax-specific env var (recommended)
export STAX_GITHUB_TOKEN="ghp_xxxx"
# Option 2: Generic GitHub token
export GITHUB_TOKEN="ghp_xxxx"
# Option 3: Use stax auth command (saves to credentials file)
stax auth
```
## Migrating from freephite
stax uses the same metadata format as freephite, so migration is instant - just install stax and your existing stacks work:
```bash
# Your existing fp stacks just work
stax s # shows your stack
stax rs # syncs repo
stax ss # submits PRs
```
## Benchmarks
Measured with `hyperfine --shell=none` on a 10-branch stack:
| `stax ls` | 21ms |
| `fp ls` | 363ms |
<details>
<summary>Full benchmark output</summary>
```
$ hyperfine 'stax ls' 'fp ls' --shell=none
Benchmark 1: stax ls
Time (mean ± σ): 21.5 ms ± 2.0 ms
Benchmark 2: fp ls
Time (mean ± σ): 362.7 ms ± 16.1 ms
Summary
stax ls ran 16.87 ± 1.71 times faster than fp ls
```
</details>
## License
MIT