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 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:
$ 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”:

This submits each change as an individual pull request:

After a while, I receive a couple of review comments, not on my entire submission, but rather, on each individual change. Additionally, the reviewer is happy with my first change, and has gone ahead and merged that:



Let us address the first review:
can you use the new
maps.Copy
api here?
$ jj log
@ so set: introduce set difference push-soqmukrvport
├ sq set: introduce set intersection
├ mk set: introduce set union
├ my set: introduce basic set operations
~
# let's edit the implementation of `Union`
$ jj edit mk
# hack, hack, hack
$ jj log
Rebased 2 descendant commits onto updated working copy
├ so set: introduce set difference push-soqmukrvport*
├ sq set: introduce set intersection
@ mk set: introduce set union
├ my set: introduce basic set operations
~
Next, let us address the bug:
there is a logic bug here, the condition should be negated.
# let's edit the implementation of `Difference`
$ jj edit so
# hack, hack, hack
We are done addressing reviews:
$ jj git push
Changes to push to origin:
Move sideways bookmark push-soqmukrvport from fc06362295bd to dfe2750f6d40
Upon resubmitting the PR for review, Tangled is able to accurately trace the commit across rewrites, using jujutsu change-ids, and map it to the corresponding PR:
Of note here are a few things:
- The initial submission is still visible under
round #0
- By resubmitting, the round has simply advanced to
round #1
- There is a helpful “interdiff” button to look at the difference between the two submissions
The individual diffs are still available, but most importantly, the reviewer can view the evolution of a change by hitting the interdiff button:

Indeed, the logic bug has been addressed!
start stacking today
If you are a jujutsu user, you can enable this flag on more recent versions of jujutsu:
λ jj --version
jj 0.29.0-8c7ca30074767257d75e3842581b61e764d022cf
# -- in your config.toml file --
[git]
write-change-id-header = true
This feature writes change-id
headers directly into the
git commit object, and is visible to code forges upon push,
and allows you to stack your PRs on Tangled.