---
atroot: true
template:
slug: stacking
title: jujutsu on tangled
subtitle: tangled now supports jujutsu change-ids!
date: 2025-06-02
image: /static/img/interdiff_difference.jpeg
authors:
- name: Akshay
email: akshay@tangled.sh
handle: oppi.li
draft: false
---
Jujutsu is built around structuring your work into
meaningful commits. Naturally, during code-review, you'd
expect reviewers to be able to comment on individual
commits, and also see the evolution of a commit over time,
as reviews are addressed. We set out to natively support
this model of code-review on Tangled.
Tangled is a new social-enabled Git collaboration platform,
[read our intro](/intro) for more about the project.
For starters, I would like to contrast the two schools of
code-review, the "diff-soup" model and the interdiff model.
## the diff-soup model
When you create a PR on traditional code forges (GitHub
specifically), the UX implicitly encourages you to address
your code review by *adding commits* on top of your original
set of changes:
- GitHub's "Apply Suggestion" button directly commits the
suggestion into your PR
- GitHub only shows you the diff of all files at once by
default
- It is difficult to know what changed across force pushes
Consider a hypothetical PR that adds 3 commits:
```
[c] implement new feature across the board (HEAD)
|
[b] introduce new feature
|
[a] some small refactor
```
And when only newly added commits are easy to review, this
is what ends up happening:
```
[f] formatting & linting (HEAD)
|
[e] update name of new feature
|
[d] fix bug in refactor
|
[c] implement new feature across the board
|
[b] introduce new feature
|
[a] some small refactor
```
It is impossible to tell what addresses what at a glance,
there is an implicit relation between each change:
```
[f] formatting & linting
|
[e] update name of new feature -------------.
| |
[d] fix bug in refactor -----------. |
| | |
[c] implement new feature across the board |
| | |
[b] introduce new feature <-----------------'
| |
[a] some small refactor <----------'
```
This has the downside of clobbering the output of `git
blame` (if there is a bug in the new feature, you will first
land on `e`, and upon digging further, you will land on
`b`). This becomes incredibly tricky to navigate if reviews
go on through multiple cycles.
## the interdiff model
With jujutsu however, you have the tools at hand to
fearlessly edit, split, squash and rework old commits (you
can absolutely achieve this with git and interactive
rebasing, but it is certainly not trivial).
Let's try that again:
```
[c] implement new feature across the board (HEAD)
|
[b] introduce new feature
|
[a] some small refactor
```
To fix the bug in the refactor:
```
$ jj edit a
Working copy (@) now at: [a] some small refactor
$ # hack hack hack
$ jj log -r a::
Rebased 2 descendant commits onto updated working copy
[c] implement new feature across the board (HEAD)
|
[b] introduce new feature
|
[a] some small refactor
```
Jujutsu automatically rebases the descendants without having
to lift a finger. Brilliant! You can repeat the same
exercise for all review comments, and effectively, your
PR will have evolved like so:
```
a -> b -> c initial attempt
| | |
v v v
a' -> b' -> c' after first cycle of reviews
```
## the catch
If you use `git rebase`, you will know that it modifies
history and therefore changes the commit SHA. How then,
should one tell the difference between the "old" and "new"
state of affairs?
Tools like `git-range-diff` make use of a variety of
text-based heuristics to roughly match `a` to `a'` and `b`
to `b'` etc.
Jujutsu however, works around this by assigning stable
"change id"s to each change (which internally point to a git
commit, if you use the git backing). If you edit a commit,
its SHA changes, but its change-id remains the same.
And this is the essence of our new stacked PRs feature!
## interdiff code review on tangled
To really explain how this works, let's start with a [new
codebase](https://tangled.sh/@oppi.li/stacking-demo/):
```
$ jj git init --colocate
# -- initialize codebase --
$ jj log
@ n set: introduce Set type main HEAD 1h
```
I have kicked things off by creating a new go module that
adds a `HashSet` data structure. My first changeset
introduces some basic set operations:
```
$ jj log
@ so set: introduce set difference HEAD
├ sq set: introduce set intersection
├ mk set: introduce set union
├ my set: introduce basic set operations
~
$ jj git push -c @
Changes to push to origin:
Add bookmark push-soqmukrvport to fc06362295bd
```
When submitting a pull request, select "Submit as stacked PRs":