git-stk is my take on building a tool to support the increasingly popular “stacking” workflow in Git.
Stacking helps parallelize development and code review into smaller, easier-to-review changesets, and tools
like git-stk exist to streamline the process of creating, updating, and otherwise managing those “stacks”.
git-stk keeps stacks as ordinary Git branches, with stack parent metadata stored locally in .git/config as
branch.<name>.stkParent; GitHub PR bases or GitLab MR target branches (or the Gitea/Forgejo equivalents)
can be used to reconstruct that metadata.
Nicely formatted/auto-managed PR descriptions for the stack
TL;DR
Don’t feel like reading? Can’t wait to try it out? Paste the following
into your terminal and run git stk help to get an overview.
# linux and macos
curl https://larakelley.com/sh/git-stk | bash
# alternatively, homebrew
brew install lararosekelley/tap/git-stk
# windows (powershell - spawn new shell after install)
powershell -ExecutionPolicy Bypass -c "irm https://github.com/lararosekelley/git-stk/releases/latest/download/git-stk-installer.ps1 | iex"
The tool works on Windows, macOS, and Linux, integrates right into the git CLI
you know and (potentially) love, and supports the following shells:
bashfishzshelvishPowerShellGit Bash
The code is available on GitHub and the project is published to crates.io. Have feedback? Post an issue here. Enjoy!
For a thorough overview of stacking, check out the stacking.dev webpage.
What made me a convert
I’ll leave the thorough overview to the post linked above. I was a late adopter to this workflow, but once I tried it out for a large (thousands of lines, a few dozen commits) changeset, I never wanted to go back.
Our company has had a paid Graphite subscription for a while, and a few folks who advocate for it heavily at the company. It’s easy to understand why, especially in our LLM-dominated era; pull requests are getting bigger, showing up more often, and even coming from sources outside of the engineering team now and then these days.
Another stacked branches tool… why?
While there is no shortage of great options out there for adopting a stacking workflow, I ended up building git-stk
anyway for a few reasons. I read about or tried a bunch of tools first:
Without going into depth on each tool’s pros and cons, I’d say the themes I kept running into that turned me off were a combination of the following:
- The tool was a paid product (Graphite)
- The tool did too much, or tried to replace
gititself (Jujutsu, Sapling, Git Town,git-branchless) - The tool supported working with remotes, but only GitHub (
spr,ghstack)
Of all the tools I looked at, I found Graphite to be the most impressive (granted, it’s a paid product),
and git-branchless the closest to what I had in mind for an open-source tool. I was close to going with
git-branchless, as it appears to be an impressive and well-tested project, but it still did a lot more than
I wanted at the end of the day.
I liked the idea of keeping everything in my project under a single stk namespace
to avoid any rubbing up against the rest of git.
I’d be remiss not to mention that you don’t need a dedicated tool to adopt stacking into your day-to-day; see
this post about using the --update-refs
argument to git rebase, which would be a good way to understand the fundamentals regardless.
It should go without saying that I appreciate all the wonderful prior art that allowed me to design git-stk
quickly. See git stk credits for my tribute to the tools above. ❤️
What makes git-stk perfect (for me)
I’m biased 😅, but beyond it feeling nice to have my own little playground that I can re-arrange and
change how I see fit, I’m happy with the balance I struck in making a tool that feels like a slam-dunk to make a
core part of my day-to-day workflow, but can also be entirely forgotten if I only want to use git the
way I got used to for 10+ years before I even knew what a stacked branch workflow was.
Using the tool
I put a lot of care into making the developer experience dead-simple, especially for the “happy path”, and
took full advantage of the gh, glab, and tea CLI tools so that folks could forget about having to use both
git and those tools in tandem 99% of the time. To illustrate what I mean, here are a few examples,
presented from simplest to most complex.
What it can do
The whole surface lives under git stk. Any given day, you’ll touch a handful of
these, but the full set is here:
Build and move around a stack
new: create a child branch on top of wherever you standsplit: split one branch’s commits into a stack of branches, bottom-uplist: draw the stack, trunk at the bottomup/down/top/bottom: walk the stack without typing branch namesstatus: local and remote state for a branchparent/children: print a branch’s parent or its children
Submit and land (GitHub, GitLab, Gitea, or Forgejo)
submit: open or update a review per branch, with a live stack overview in every descriptionmerge: merge the bottom review, then sync the rest of the stack behind itsync: fetch trunk, refresh metadata from reviews, clean up merged branches, re-stack, and pushview/review: jump to a branch’s PR/MR, or print it
Keep a stack healthy
restack: rebase every descendant back onto its parent from anywhere in the stack (continue/abortwhen a conflict stops it)absorb: route staged fixes back into the commits that introduced the lines they touch, viagit blameadopt/detach: fold a hand-made branch into a stack, or drop one back out (metadata only, nothing rewritten)rename: rename a branch and re-target its childrenrepair: rebuild lost stack metadata from review bases and branch ancestryundo: reverse the last stack-rewriting command, restoring branch tips and metadatarun: run a command on every branch bottom-up, so you know each layer is green on its own before you submit
Everything else (cleanup, config, provider, completions, setup, upgrade, downgrade, uninstall,
credits) is plumbing. And git stk guide walks you through any of it in a throwaway sandbox! 🏖️
Simple usage
Note that these examples may grow a little out of date as the tool evolves, but you’ll still get the idea 😄
In this example, I’m acting as the sole maintainer of a repository, and using git-stk to keep things
organized and the history clean. Let’s simulate working on a project (we’re using GitHub here). I know at the
start here that I’d like to stack these branches on top of each other, and things will move in
a pretty linear fashion here:
# Each branch carries one reviewable change and knows its parent.
# `new` creates a child of wherever you stand:
$ git stk new feature/login
created feature/login with parent main
# edit login.txt, then git commit -m "add login form"
$ git stk new feature/avatar
created feature/avatar with parent feature/login
# edit avatar.txt, then git commit -m "add avatars"
# Two branches, stacked. `list` draws the pile, trunk at the bottom:
$ git stk list
◉ feature/avatar (+1/-0)
○ feature/login (+1/-0)
○ main (trunk)
Now, we submit everything and open PRs on GitHub:
# One command opens (or updates) a review per branch, parent-first,
# and writes a live stack overview into every description:
$ git stk submit --stack
created feature/login -> main
demo://review/1
created feature/avatar -> feature/login
demo://review/2
updated stack note in #1
updated stack note in #2
submit complete: 2 created, 0 updated, 0 skipped
Let’s double-check where we stand with git stk status:
$ git stk status
branch: feature/avatar
parent: feature/login
children: none
provider: demo (config)
review: #2 open feature/avatar -> feature/login
url: demo://review/2
In a real scenario, you’d then hop over to GitHub and check out your new PRs. Graphite users in particular
should appreciate the formatted stack reference included in the PR description; the tool will also keep that
list up-to-date as PRs are merged and we sync/re-submit things.
# Review feedback lands on the bottom branch:
$ git stk down
switched to feature/login
# edit login.txt, then git commit -m "add remember me"
# The child is now behind its parent - `list` notices:
$ git stk list
○ feature/avatar (#2, +1/-0)
◉ feature/login (#1, +2/-0)
○ main (trunk)
hint: feature/avatar is 1 commit behind feature/login - run `git stk restack`
# `restack` rebases every descendant back onto its parent:
$ git stk restack
feature/login already up to date with main
rebasing feature/avatar onto feature/login
restack complete
remote branches may be stale; push them with:
git push --force-with-lease origin feature/login feature/avatar
$ git stk top
feature/avatar is already at the top of the stack
Once the dust settles, we merge everything:
# `merge --all` repeats merge-bottom-then-sync until the stack is
# complete: children retarget, merged branches vanish, the overview
# in every review restyles as history accumulates:
$ git stk merge --all -y
squashed feature/login into main
merged add login form (#1)
no remote origin; skipped fetch
feature/login: review #1 is merged
sync complete: 0 synced, 0 skipped
updated stack note in #1
updated stack note in #2
will retarget feature/avatar -> main
will update review feature/avatar -> main (#2)
will detach feature/login
will delete branch feature/login
rebasing feature/avatar onto main
restack complete
remote branches may be stale; push them with:
git push --force-with-lease origin feature/avatar
next up: feature/avatar -> #2 demo://review/2
squashed feature/avatar into main
merged add avatars (#2)
no remote origin; skipped fetch
feature/avatar: review #2 is merged
sync complete: 0 synced, 0 skipped
updated stack note in #2
updated stack note in #1
switched to main
will detach feature/avatar
will delete branch feature/avatar
stack complete: everything merged into main
merge complete: 2 of 2 reviews merged
And, we’re done - back to main with everything merged and cleaned up:
$ git stk list
no stacked branches
create one on top of the current branch with `git stk new <branch>`
# That is the whole loop: new -> commit -> submit -> merge.
# On a real repo the provider is detected from your remote; day to day
# you mostly run `git stk new`, `git stk submit --stack`, and
# `git stk merge --all`. `git stk status` and the hints fill the gaps.
Pretty nice, huh? But, especially when working on large teams, things are rarely this simple. Run git stk guide
and go through the full set of examples to understand repairing a busted stack and resolving conflicts.
The tool is as thoroughly tested as I can manage; it runs unit and end-to-end tests against real, ephemeral GitHub and GitLab repositories before any release is published, hitting every supported operating system (Linux, macOS, and Windows) and a range of workflows. I also have access to actual hardware running each OS, and test manually there too. Most development happens on my Linux (Fedora) machine against the project’s GitHub repo for some solid dog-fooding.
If these examples (which come directly from the built-in git stk guide 😉 tutorial) didn’t sell you on the tool,
I’d love to hear why in the comments below!

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.