Why we made this

Stacked diffs aren't a new idea. The reason this project exists is that the tooling around it is either closed-source SaaS or fragmented. giff stack is an attempt at one open, self-hostable, opinionated implementation.

The problem

Big PRs review slowly. A 2,000-line PR is a multi-day game of comment ping-pong: the reviewer hits an issue at line 80, asks a clarifying question, waits for an answer, comes back, finds a related issue at line 1,400. Meanwhile the author can't merge anything. Meanwhile the trunk moves and the rebase pile-up grows.

Stacked diffs solve this by letting you split one logical change into a chain of small, reviewable layers — each independently mergeable, each building on the previous. The reviewer picks up layer 1 today, layer 2 when ready, layer 3 next week. Five small reviews running in parallel beat one big review running in serial.

flowchart LR
  subgraph BIG[ Big PR — serial ]
    direction TB
    big["<b>one PR</b><br/><span style='color:#86868b;font-size:12px'>2,000 LOC<br/>1 review pass</span>"]:::big
  end
  subgraph STACK[ Stacked — parallel ]
    direction BT
    pr1["PR 1<br/><span style='color:#86868b;font-size:11px'>merged</span>"]:::done
    pr2["PR 2<br/><span style='color:#86868b;font-size:11px'>merged</span>"]:::done
    pr3["PR 3<br/><span style='color:#86868b;font-size:11px'>approved</span>"]
    pr4["PR 4<br/><span style='color:#86868b;font-size:11px'>under review</span>"]:::brand
    pr1 --> pr2 --> pr3 --> pr4
  end
  classDef big fill:#ffffff,stroke:hsl(220,13%,86%),color:hsl(220,9%,12%);
  classDef done fill:hsl(220,13%,95%),stroke:hsl(220,13%,86%),color:hsl(220,8%,46%);
  classDef brand fill:#ff0035,stroke:#ff0035,color:#ffffff;
Big PR (left) vs stack of small PRs (right). Same total work; the stacked version reviews in parallel.

Why not just use [existing tool]

The honest comparison set:

Graphite

Closest match. Has a free open-source CLI (gt) and a paid hosted SaaS for the dashboard, reviewer pad, and merge queue. We borrowed many primitives from their model. The gaps that motivated giff stack:

  • The dashboard and merge automation are SaaS-only — your token + PR metadata live in their cloud.
  • No self-hosted option. If your repo is in a regulated environment that can't send token+repo data to a third party, you're stuck.
  • Per-user pricing scales the wrong way for small teams using stacked diffs heavily.

ghstack

Meta's open-source CLI. Closest in spirit; giff stack's data model borrows from it. ghstack is excellent at the CLI layer and stops there — no dashboard, no reconciliation service. If the CLI is all you want, ghstack is a fine choice.

git-spice / git-branchless

Adjacent projects that overlap on primitives but solve slightly different problems (branch-tracking ergonomics; advanced rebase). Worth knowing about; not direct replacements.

GitButler

Different abstraction entirely. GitButler does virtual branches — multiple unrelated changes simultaneously in one working copy, "publish" them as separate PRs whenever. giff does real branches in a stack — one logical change broken into reviewable layers. Different problems. Use GitButler if "I'm juggling three independent things at once" is your pain; use giff stack if "I have one big feature to break up" is.

Mergify

PR automation engine — handles auto-merge, merge queues, branch protection rules, etc. Doesn't manage stacks. The giff stack runner overlaps with Mergify's auto-merge feature for the specific case of "merge the bottom of a stack." For anything more complex, Mergify is the better tool.

What we believe

  • Real branches over virtual ones. Every frame is a branch you can see in git branch. No magic working-copy semantics. Your stack survives giff being uninstalled — you just lose the metadata file, not the work.
  • One commit per frame. Frames represent logical layers, not commit history. A frame with three WIP commits is a frame that hasn't been polished yet; rebases get worse with every fixup commit. The pre-commit hook makes the rule machine-enforced rather than aspirational.
  • GitHub is the source of truth. Local .git/stacked.toml is a cache. The runner's SQLite is a cache. The web dashboard reads from GitHub directly. Anything that holds local state holds it temporarily and reconciles on demand. Lose the cache, sync, get the cache back.
  • Self-hostable means actually self-hostable. The runner runs in any Docker host you control. Token and metadata never leave your infrastructure. If the project disappears tomorrow your install keeps working.
  • Open source by default. The CLI, the web dashboard, and the runner are all in the same repo, all under the same license. There's no "open core" — the parts you want, you can have, run, and modify.
  • Honest about limits. See the limitations page.

The path

The roadmap, in priority order:

  1. Phase 1 (today): CLI + dashboard + self-hosted runner. Single-tenant. Fully open source. Goal: useful for one team.
  2. Phase 2 (later): Hosted version of the same runner — single-tenant per customer, paid via subscription. The bar to start: do people deploy Phase 1? Are they emailing about issues?
  3. Phase 3 (much later): Multi-tenant SaaS, real auth, billing, org model. Only if Phase 2 validates demand.
giff stack · open source · MIT Source