Git Branches vs Jujutsu Changes: Two Models for Stacks
In this piece
Halfway through a three-layer stack, a reviewer asks for one field in the bottom layer to change. Amend the commit, force-push the first branch, then rebase the second and third onto it. The rewritten commits have new identities, even though the dependency chain has not changed.
Why does keeping that chain intact feel like rebuilding it?
What stays the same while everything moves
The easy explanation is nicer rebase commands in Jujutsu. True, and beside the point. GitHub’s stacked pull request preview (the subject of the first article in this series) cascades the rebase for me. After a partial merge it rebases the branches above, retargets the next pull request, and applies the same checks to every layer. The mechanical pain shrank. Something still nagged: work I had finished kept turning into history the repository had never seen.
The difference is what holds its identity while you rewrite everything around it.
Two ways to name a piece of work
Take the example running through this series: a small Go change in three layers.
In Git, each commit is identified by a hash derived from its contents, metadata, and parents. A review layer may contain one or more commits, while a branch gives the layer a movable name. Rewriting a lower commit creates new commit IDs for that commit and its rewritten descendants; the branch names remain, but they must move to the new commits.
In Jujutsu, every commit also carries a change ID. That ID normally survives amendments, rebases, and fixes squashed into the change, while the underlying Git commit ID changes.
Git preserves branch names across a rewrite. Jujutsu additionally preserves the identity of each evolving change.
Why the two stacks diverge
In Git, an upper branch remains based on the old lower commit after the lower layer is rewritten or squash-merged. That old commit usually remains reachable through the upper branch, but it no longer matches the target branch’s new history. Rebasing creates replacement commits and moves the branch pointers. GitHub’s preview automates that cascade and retargets the pull requests.
In Jujutsu, rewriting the bottom change automatically rewrites its descendants. Their change IDs and order normally remain stable, while Jujutsu records any resulting conflicts in the affected commits. You still resolve conflicts and verify the code, but you do not reconstruct which evolving change each rewritten commit represents.
git
# rename a field in Request-ID (bottom layer) - a1b2c3d Request-ID type + 7f9c1a2 Request-ID type branch1 · force-push - b2c3d4e HTTP middleware + 3e8d0b1 HTTP middleware branch2 · rebase + force-push - c3d4e5f Logging + 91a4f7c Logging branch3 · rebase + force-push
jj
# rename a field in change kxnyzmwl kxnyzmwl Request-ID type amended · ID held qpsrumwx HTTP middleware rebased · ID held wmrt6xqp Logging rebased · ID held
The operation log (jj op log) records repository operations, and jj undo can reverse them. That model usually makes a multi-step rewrite easier to undo than reconstructing it from Git’s reflogs.
What changes when identity survives rewrites
Cheap, reversible rewrites make it easier to jj squash a fix into the change where it belongs, jj split an overgrown change, or reorder changes after the dependency structure becomes clear.
Git can perform the same rewrites. They create new commit hashes, require branch updates, and can disrupt review continuity after a force-push. Jujutsu removes some of that bookkeeping by retaining change IDs.
A stable change identity does not guarantee correct code. It makes the revised history easier to follow and inspect.
One shared example, two maintenance paths
Keep the request-ID example small enough to inspect. The type sits at the bottom. Middleware depends on the type. Logging depends on middleware. Now change the bottom layer after all three layers exist.
With Git branches, the request starts on the lower branch. You amend or add the correction there, then the branches above it no longer have the right parent commits. The branch names still exist, but their histories describe an older foundation. A rebase produces replacement commits for every affected layer. The remote branch tips move when you force-push or when GitHub’s stack tooling updates them.
GitHub’s native stack support improves that sequence. A gh stack rebase can cascade the rebases locally. gh stack sync fetches the trunk, reconciles the local stack, pushes changed branch tips, and refreshes the linked pull requests. After a lower pull request merges, GitHub can rebase and retarget the remaining stack. The reviewer sees ordinary Git branches and ordinary pull requests, with the base branch carrying the dependency order.
That is enough for many teams. It reduces the manual work without requiring every contributor to learn another local history model.
With Jujutsu, the same change begins in the local graph. The author edits the bottom change with jj edit, and Jujutsu rewrites the descendants. The Git commits still receive new hashes. The Jujutsu change IDs keep identifying the evolving work. A later jj log can say that “Add request-ID middleware” is the same local change before and after the foundation changed, even though its Git revision differs.
The stable ID helps most while the work is not yet ready for review. You can split a broad change, squash a correction into the layer that owns it, abandon an experiment, or rebase part of the graph before you create any branch. Once a layer earns review, a bookmark exports its current revision as a Git branch. GitHub then takes over the part it knows: pull requests, approvals, checks, base branches, and merges.
The tools do not disagree about Git history. They disagree about where an author should keep the in-progress model.
Identity, ownership, and recovery
The difference becomes clearer when each object has one job:
| Concern | Git plus GitHub stack | Jujutsu plus GitHub stack |
|---|---|---|
| Local unit of work | Commit and branch tip | Mutable change with a change ID |
| Local dependency order | Parent commits and branch bases | Parent changes in the local graph |
| Shared review unit | Branch and pull request | Bookmark-exported branch and pull request |
| Rewrite result | New commit hashes; branch tips move | New commit hashes; change IDs normally persist |
| Remote stack relationship | GitHub stores the PR ordering | GitHub stores the PR ordering after branches are exported |
| Undo | Reflog, reset, revert, or a new corrective commit | jj undo or the operation log |
| Team requirement | Git and GitHub | Git and GitHub; Jujutsu is optional for each author |
The table is not a scorecard. Git branches are a durable interchange format. They are what GitHub receives, what CI checks out, what code owners recognize, and what a teammate can inspect from any normal clone. Jujutsu does not replace that shared substrate.
Jujutsu changes solve a narrower local problem: an author needs to revise a dependency graph without treating every revised commit hash as a new conceptual object. The graph provides a stable address for a piece of work while its implementation moves.
That distinction changes recovery, but it does not erase it. A conflict still represents incompatible code. If a bottom API changes, middleware may still need a deliberate repair. Jujutsu can preserve the conflicted descendant and its change ID; it cannot decide whether the new API still supports the old middleware behavior. GitHub can rebase an upper branch after a partial merge; it cannot tell whether the resulting diff still has a sensible review boundary.
In both models, recovery starts at the bottom:
- Put the correction in the layer that owns the violated assumption.
- Recompute the descendants:
gh stack rebaseor Jujutsu’s automatic descendant rewrite. - Inspect each affected diff against its intended parent.
- Resolve concrete conflicts where the changed assumption reaches them.
- Push only revisions that still state a reviewable claim.
The operational difference is where the author spends attention. A Git-first workflow asks, “Which commits and branch tips now need to move?” A Jujutsu workflow asks, “Which changes remain in the graph, and which exported bookmarks should move after I am satisfied?” Both questions matter. The second postpones the remote part until the author wants an audience.
The cost of a second local model
Jujutsu is not free. A team needs installation instructions, a small shared vocabulary, and a policy for interacting with colocated repositories. Its own documentation recommends using Jujutsu for mutations and Git primarily for inspection in a colocated workspace. Mixing git rebase, git commit --amend, and jj rewrites without understanding their interaction makes ref state harder to read.
The learning curve is also real. An author familiar with branch tips, staging, and git rebase --continue must learn that the working copy is a change, that there is no staging area, that bookmarks are only needed for publishing, and that jj undo rolls back a repository operation rather than merely changing files. That is a useful model only when the author spends enough time reshaping stacked work to recover the cost.
GitHub’s stack workflow has its own limits. It remains a public preview. It works with same-repository branches, not fork-based contribution chains. The GitHub CLI extension handles authoring and synchronization; GitHub Desktop does not currently provide the same workflow. Repository rules, required checks, review ownership, and merge queues still govern the final merge.
These constraints point toward a conservative adoption rule. Standardize the review surface first. Every stack should end up as ordinary Git branches and pull requests with understandable bases. Let individual authors adopt Jujutsu when their local revision loop is expensive enough to justify it. Do not require Jujutsu merely because a team has small, stable stacks.
Graphite belongs in a third category. It is a Git-oriented authoring and review workflow around stacks, not evidence that either GitHub’s native model or Jujutsu’s local model produces a universal productivity result. A team can evaluate it on its own workflow, but the decision remains about ownership: who manages local history, who presents review order, and who maintains the shared pull-request surface.
Choose the smallest model that removes your real cost
Start with GitHub stacks when the hard problem is shared review coordination. The branch bases, stack map, partial merge behavior, and per-layer checks give reviewers an ordered surface without asking them to understand a local tool. A shallow stack that changes once or twice before merge does not need a second graph model.
Add Jujutsu for authors whose hard problem is local revision. That may be a feature being split after discovery, a bottom-layer API that changes under active work, or a sequence of experiments where the final review boundaries are not clear on day one. The author can keep the full graph local, export bookmarks only when each layer earns review, and use GitHub’s stack support for the shared part.
Use one normal pull request when there is one decision. Use independent pull requests when the pieces have no dependency order. A stack is useful only when each layer depends on the one below it and can receive a meaningful review in that order.
Decision table
| Team condition | Start with | Reason |
|---|---|---|
| Two or three stable layers, GitHub-native reviewers, few rewrites | GitHub stack | The platform removes the branch and pull-request bookkeeping already. |
| A feature changes shape repeatedly before its first review | Jujutsu locally, then GitHub stack | Change IDs and local rewrites preserve the author’s model while the boundary is still unsettled. |
| Contributors work from forks | Normal pull requests | Native stacks require same-repository branches in the current preview. |
| Several teams own different layers | GitHub stack plus explicit review ownership | The shared pull-request surface and base relationships need to remain visible to each owner. |
| Authors need a reliable local undo path | Jujutsu locally | The operation log and jj undo make experimentation easier to reverse. |
| The work has no mergeable lower layer | One pull request | A chain would add branches and CI without creating a separate decision. |
The comparison is observable in the same public request-ID repository. In the GitHub-stack run, changing the bottom branch required an explicit gh stack rebase --upstack --no-trunk followed by gh stack push; both upper branch tips moved. After the bottom pull request merged, gh stack sync rebased the remaining branches and updated the remote stack.
In the Jujutsu run, editing the middleware change caused Jujutsu to report one rebased descendant. The middleware and logging change IDs held their values, while their Git commit hashes changed. Exporting two bookmarks and invoking gh stack link then created ordinary GitHub pull requests with a middleware-to-main base and logging-to-middleware base.
The experiment demonstrates object ownership, not a speed claim. The GitHub workflow kept review structure synchronized. The Jujutsu workflow kept local change identity synchronized. Both still required an author to inspect the rewritten diffs and decide whether the changed layers deserved to remain separate.
The practical check is simple. If a corrected lower layer causes more time spent locating old branch tips than reasoning about the new diff, the author has a local-history problem. If the work rarely rewrites after review begins, GitHub’s ordinary branch model is usually the smaller tool.
Choose the model by observed maintenance cost.
The choice is not “GitHub or Jujutsu.” GitHub remains the shared review system in both paths. The question is whether local stack manipulation has become costly enough to deserve a model with stable change identity.
Try the comparison on one real three-layer change. Record the commit hashes and Jujutsu change IDs before changing the foundation. Make the same small correction in each workflow. Then compare the final pull request bases, the diffs, the conflicts, and the commands each author needed. That evidence will tell you more than a feature checklist.
The next article, Control Review and CI Costs When Adopting Stacked PRs, treats the shared side of the choice: review ownership, CI cost, merge timing, and the cases where a single pull request is better.
Sources
Filed under
Explore this subject