Skip to content

Repository files navigation

git-stk logo

git-stk

crates.io

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.

Formatted and automatically managed PR descriptions

Reporting issues

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

Install using the official install script (except for PowerShell), which downloads the pre-built binary:

curl https://larakelley.com/sh/git-stk | bash

Or, with Homebrew on macOS:

brew install lararosekelley/tap/git-stk

With 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.sha256

The 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 upgrade

To 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.

Quickstart

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 command

New 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.

Shell Completions

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.

Worktrees

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.

Install For Development

just install
just check
cargo install --path .

After installation, Git can use the binary as a sub-command:

git stk list

Commands

Git'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 one

Every flag, and the behavior behind each command, lives in docs/COMMANDS.md.

Configuration

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-worktrees

The 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 config

Providers

Provider 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.

Generated Assets

Shell completions and a man page can be generated with:

just generate-assets

Generated files are written under target/generated.

Project Tasks

just build
just test
just lint
just check

License

Copyright (c) 2026 Lara Kelley. MIT License. See LICENSE.

About

Git-native stacked branch workflow helper, with GitHub, GitLab, and Gitea integration

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages