Git-native stacked branch workflow helper with GitHub, GitLab, and Gitea support.
git-stk keeps stacks as ordinary Git branches. Stack parent metadata is stored locally in .git/config as
branch.<name>.stkParent, and GitHub/Gitea PR bases or GitLab MR target branches can be used to reconstruct that metadata.
Planned work and known issues are tracked in GitHub issues.
Feel free to report bugs, feedback, feature requests, or ask questions there; just be polite 😉
Install using the official install script (except for PowerShell), which downloads the pre-built binary:
curl https://larakelley.com/sh/git-stk | bashOr, with Homebrew on macOS:
brew install lararosekelley/tap/git-stkWith a Rust toolchain, cargo install git-stk --locked builds from source, or cargo binstall git-stk
fetches the pre-built binary without compiling.
On native Windows, use the PowerShell installer instead:
powershell -ExecutionPolicy Bypass -c "irm https://github.com/lararosekelley/git-stk/releases/latest/download/git-stk-installer.ps1 | iex"The shell command runs install.sh - a thin wrapper around the
cargo-dist-generated git-stk-installer.sh; PowerShell
fetches the matching git-stk-installer.ps1. Every release attaches the pre-built binaries, both
installers, and per-file .sha256 checksums to
GitHub Releases, so you can download and verify a
binary directly instead of piping to a shell:
# Linux x86_64, for example
sha256sum -c git-stk-x86_64-unknown-linux-musl.tar.xz.sha256The Linux builds are static (musl), so they run anywhere - glibc, Alpine, or an older distro.
Prerequisites: review commands drive the GitHub (gh), GitLab (glab), or Gitea/Forgejo (tea) CLI,
so install the one you use and sign in (gh auth login / glab auth login / tea login add). git-stk
needs git 2.38 or newer (for rebase --update-refs). The local stack commands work without any CLI.
Then install the man page and wire up shell completions (idempotent; prompts before touching your shell rc):
git stk setup [-y] [--refresh]Upgrade an installer-managed copy with:
git stk upgradeTo remove git-stk, git stk uninstall reverses setup and the installer: it strips the completion line it
added to your shell rc, deletes the man page, and removes the config/receipt directory (--dry-run to
preview, -y to skip the prompt). It prints how to remove the binary itself rather than deleting it - a
running program can't reliably delete its own executable, and a cargo install / Homebrew copy should go
through cargo uninstall git-stk or brew uninstall git-stk. Per-repo stk.* config and branch metadata
are left untouched.
git stk new feature/api # stack a branch on the current one
# ...commit work...
git stk new feature/web # stack another on top of it
# ...commit work...
git stk submit --stack # open a PR/MR for each branch, in order
git stk merge --all # land them bottom-up, in one commandNew to stacking? git stk guide runs the whole loop offline in a disposable sandbox - nothing real is
touched and no account is needed. The tours: intro (create a stack, submit, restack, land it),
conflicts (resolve and continue an interrupted restack), repair (rebuild lost stack metadata),
absorb (fold review fixes into the commits that introduced them), adopt (adopt a hand-made branch, or
move one to a new parent), and undo (reverse the last stack-rewriting command). git config stk.provider demo turns any scratch repo into the same offline playground.
git stk setup configures these automatically. Completions are dynamic: the shell asks the binary for
candidates at completion time, so subcommands, flags, and even branch names complete (git stk up <TAB>
offers only the current branch's stack children). The installed binary prints its own registration script,
so completions stay in sync across upgrades:
# bash: add to ~/.bashrc (the guard keeps shell startup quiet if git-stk is removed)
command -v git-stk >/dev/null && source <(git stk completions bash)
# zsh: write to a directory on your fpath
git stk completions zsh > "${fpath[1]}/_git-stk"# PowerShell: add to $PROFILE (git stk setup does this for you on Windows)
if (Get-Command git-stk -ErrorAction SilentlyContinue) { git stk completions powershell | Out-String | Invoke-Expression }git stk setup detects bash, zsh, and fish from $SHELL (covering Git Bash and WSL), and falls back to
PowerShell on native Windows by wiring $PROFILE. Elvish is also supported via git stk completions elvish. The bash and zsh output includes a wrapper so git's own completion can complete git stk <TAB>
in addition to git-stk <TAB>. -y/--yes skips the shell-rc confirmation for non-interactive setup;
--refresh only re-renders the man page and never touches your shell rc - it is what upgrade runs with
the freshly installed binary.
--wrapper additionally defines the stk shell function whose up/down/top/bottom
cd into the worktree holding the branch, and teaches your shell to complete stk the same way it
completes git-stk. It is opt-in because it defines a new command name: setup skips it, telling you why,
if an stk executable is on your PATH or your rc file already defines one. bash and zsh only - fish
needs different syntax. Re-running with --wrapper after a plain setup merges it into the existing
block rather than appending a second one, and git stk uninstall removes the whole block either way.
git-stk understands linked worktrees. list and status name the worktree holding each branch, and the
commands that rewrite history refuse up front - before touching anything - rather than failing partway
through, because git will not rebase, delete, or check out a branch another worktree has checked out.
That last one makes navigation awkward in a worktree-per-branch layout: moving up the stack is a cd, not
a checkout, and a program cannot change its parent shell's directory. So the navigation commands take
--from-path, which prints where to go and lets the shell do the moving:
git stk setup --wrapper writes this for you (see Shell Completions); by hand it is:
# bash/zsh: add to ~/.bashrc or ~/.zshrc
stk() {
case "$1" in
up|down|top|bottom)
local dest
dest=$(git stk "$@" --from-path) || return
[ -n "$dest" ] && cd "$dest"
;;
*) git stk "$@" ;;
esac
}stk up then follows the branch wherever it lives - into another worktree when that is where it is
checked out, or an ordinary checkout when it is here (printing ., so your current directory is left
alone). The switch is still reported, on stderr, so stdout stays a single usable path. Every other
command falls through to git stk untouched, so stk works as the only name you need.
Capture the path before the cd rather than cd "$(...)" directly: a navigation that fails prints
nothing on stdout, and cd "" would add its own cd: null directory complaint on top of the error
git-stk already gave you.
just install
just check
cargo install --path .After installation, Git can use the binary as a sub-command:
git stk listGit's own narration (rebase progress, switch advice, push chatter) is captured and shown only when a
git command fails; pass -v/--verbose to any command to stream it through instead. Output is colored
when the terminal supports it; pipes and NO_COLOR turn it off. Every command
that takes --dry-run also accepts -n as a short alias.
# build the stack
git stk new <branch> # stack a new branch on the current one
git stk adopt [branch] # take an existing branch into the stack
git stk detach [branch] # drop a branch out of the stack, keeping the branch
git stk rename [branch] <to> # rename, retargeting children and the open review
git stk split # split one branch's commits into a stack
git stk absorb # fold staged fixes into the commits they belong to
# move around
git stk up [branch | n] # towards the top of the stack
git stk down [n] # towards the trunk
git stk top # jump to the leaf
git stk bottom # jump to the branch just above the trunk
git stk parent [branch] # print the stack parent
git stk children [branch] # print the stack children
git stk list # draw the stack, trunk at the bottom
git stk status [branch] # one branch: parent, children, review, hints
# keep it stacked
git stk restack # rebase every descendant back onto its parent
git stk run -- <command> # run a command on each branch, bottom-up
git stk continue # resume a restack after resolving conflicts
git stk abort # unwind a conflicted restack
git stk undo # reverse the last stack-rewriting command
git stk repair # rebuild stack metadata from reviews and ancestry
# review and land
git stk submit [--stack] # open or update a review per branch, parent-first
git stk review [branch] # print a branch's review
git stk view [branch] # open it in your browser
git stk sync # refresh metadata, clean up landed branches, restack
git stk merge [--all] # land the bottom review (or the whole stack), then sync
git stk cleanup [branch] # delete landed branches and their worktrees on demand
# setup and housekeeping
git stk config # everything stk reads or wrote
git stk provider # which provider is detected, and why
git stk setup [--wrapper] # man page, completions, optional `stk` shell function
git stk completions <shell> # print the completion script for one shell
git stk guide [topic] # interactive tours in a disposable sandbox
git stk upgrade # move to the latest release
git stk downgrade # step back to an earlier one
git stk uninstall # reverse setup and the installer
git stk credits # the stacked-workflow tools that inspired this oneEvery flag, and the behavior behind each command, lives in docs/COMMANDS.md.
All settings live under [stk] in git config, so the tool's footprint stays separated from git's own.
Everything is optional; defaults shown below:
[stk]
; Review provider: github, gitlab, gitea, or demo (offline playground).
; Default: auto-detect from the remote URL.
provider = github
; Self-hosted GitLab host to detect as GitLab alongside gitlab.com (a bare
; host or a full URL). `glab` picks up the host from the remote itself.
; Default: gitlab.com only.
gitlabHost = gitlab.example.com
; Self-hosted Gitea/Forgejo host to detect as Gitea alongside gitea.com and
; codeberg.org (a bare host or a full URL). `tea` picks up the host itself.
; Default: gitea.com and codeberg.org only.
giteaHost = gitea.example.com
; Remote used for provider detection and pushes. Default: origin.
remote = origin
; Pass --update-refs to git rebase during restack. Default: false.
updateRefs = true
; Fetch the trunk from the remote before restacking, so branches rebase
; onto its latest tip. Default: false.
fetchBeforeRestack = true
; Force-push (with lease) rebased branches after restack (also the restack
; step inside sync and merge). Default: false.
pushOnRestack = true
; Push branches (-u --force-with-lease) before submitting reviews. Default: false.
pushOnSubmit = true
; Bare `submit` submits the whole stack instead of one branch. Default: false.
submitStack = true
; `sync` and `cleanup` also clean up branches whose review was closed
; without merging, not just merged ones. Default: false.
cleanClosed = true
; Strategy for `merge`: squash, rebase, or merge. Default: squash.
mergeStrategy = squash
; `merge --all` waits for each review's checks before merging it. Default: false.
mergeWait = true
; Seconds `merge --wait` polls a review's checks before giving up. 0 waits
; indefinitely. Default: 1800 (30m).
checkTimeout = 1800
; Open new reviews as drafts. Default: false.
submitDraft = true
; Seed a new review's body from the repo's PR/MR template (GitHub/Gitea
; PULL_REQUEST_TEMPLATE, GitLab's Default.md) instead of replacing it.
; Default: true.
usePrTemplate = false
; `absorb` also folds unstaged tracked edits, not just staged ones. Default: false.
absorbIncludeUnstaged = true
; Skip the once-a-day check for a newer release. Default: false.
noUpdateCheck = true
; Where `new --worktree` puts a branch's worktree. Default: a
; <repo>-worktrees directory beside the repo.
worktreeDir = ~/code/myrepo-worktreesThe tool also manages per-branch metadata: branch.<name>.stkParent (the stack parent),
branch.<name>.stkBase (the recorded fork point), and - for branches made with new --worktree -
branch.<name>.stkWorktree, recording that git-stk created that worktree and so may remove it. These
are written by new, adopt, rename, sync, restack, cleanup, and repair; you normally never
touch them by hand.
Branches are the real state; the metadata is just annotation. If it is ever lost or stale, git stk repair
rebuilds it from review bases (when the provider CLI - gh/glab/tea - is available) and branch
ancestry, and verifies recorded fork points. Anything it cannot resolve safely is reported for a manual git stk adopt.
Working across machines? The parent map rides along on a shared ref (refs/stk/metadata), published
automatically whenever git-stk pushes branches (submit/restack/sync with push). On another clone,
git stk repair --from-remote fetches that ref, pulls down any of its branches you do not have yet, and
rebuilds the local metadata - no platform or open PRs required. (Local-only commits you have not pushed
still can't transfer, of course.)
While a stack-rewriting command runs (submit, merge, sync, restack, absorb, and friends) it holds
a lock at .git/stk-lock so a second git-stk run cannot rewrite the stack at the same time; read-only
commands are never blocked. If a run is killed mid-operation the lock can linger, but the next
stack-rewriting command reclaims it automatically once it sees the holding process is gone (on Windows,
remove .git/stk-lock by hand).
Inspect everything stk reads or wrote with:
git stk configProvider detection uses stk.provider first, then stk.remote, then origin. GitHub support shells out
to gh, GitLab support shells out to glab, and Gitea to tea. Authenticate those CLIs before using provider commands.
Shell completions and a man page can be generated with:
just generate-assetsGenerated files are written under target/generated.
just build
just test
just lint
just checkCopyright (c) 2026 Lara Kelley. MIT License. See LICENSE.
