Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Seeing what changed with jj diff and jj show

So far, jj st has told us which files we’ve touched, and jj log has told us which changes exist. Neither tells us what’s actually in those changes. For that, we can use jj diff.

First, let’s give ourselves something to look at. Our working copy is sitting at an empty change, so let’s make an edit to src/main.rs:

/// A "Hello, world!" program.

fn main() {
    println!("Hello, world!");
    println!("Goodbye, world!");
}

Now we can ask what we’ve done:

$ jj diff
Modified regular file src/main.rs:
   1    1: /// A "Hello, world!" program.
   2    2: 
   3    3: fn main() {
   4    4:     println!("Hello, world!");
        5:     println!("Goodbye, world!");
   5    6: }

With no arguments it shows what’s in @: the difference between your working copy and its parent. Since your working copy is a commit like any other, that’s the same thing as “what this commit contains”.

This format can take a moment to read if you’re used to git. There are two columns of line numbers: the line’s position before, and after. A line present in both gets both numbers. A line only in the new version, like our Goodbye, has an empty left column. Deleted lines are the other way round. Suppose that instead of adding a goodbye, we had replaced the hello with something else. Then we’d see:

$ jj diff
Modified regular file src/main.rs:
   1    1: /// A "Hello, world!" program.
   2    2: 
   3    3: fn main() {
   4     :     println!("Hello, world!");
        4:     println!("Hi!");
   5    5: }

Line 4 went away and a new line 4 arrived. In a terminal this is colored, and jj highlights the words that differ rather than just the lines, which is why it’s called a “color words” diff. It’s good at showing small edits inside long lines, where git’s line-at-a-time view makes you hunt.

If you’d rather have the format every other tool understands:

$ jj diff --git
diff --git a/src/main.rs b/src/main.rs
index a4fd3383fc..ba1a7af2a4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,4 +2,5 @@
 
 fn main() {
     println!("Hello, world!");
+    println!("Goodbye, world!");
 }

This is handy when we want to paste a patch somewhere. We can also use -s to get just the summary, which is the same list of names jj st shows:

$ jj diff -s
M src/main.rs

Any change, not just this one

We can use -r to show a different change. Let’s look at the parent of our working copy, the one where we added that comment:

$ jj diff -r @-
Modified regular file src/main.rs:
        1: /// A "Hello, world!" program.
        2: 
   1    3: fn main() {
   2    4:     println!("Hello, world!");
   3    5: }

There are our two new lines, with nothing in their left columns. A file that’s new in a change looks like this for its whole contents — an empty left column all the way down, since none of it was there before.

--from and --to compare two revisions that needn’t be adjacent:

$ jj diff --from @-- --to @
Modified regular file src/main.rs:
        1: /// A "Hello, world!" program.
        2: 
   1    3: fn main() {
   2    4:     println!("Hello, world!");
        5:     println!("Goodbye, world!");
   3    6: }

This is the combined effect of two changes: the comment from the parent, and the goodbye from the working copy, in one diff. I find this useful before pushing: jj diff --from 'trunk()' shows everything our branch does.

jj show

jj diff shows us the contents of a change. jj show adds who made it, when they made it, and why:

$ jj show @-
Commit ID: 7a096b8a2a7108ebfe30e05e7d38ee0dc77b9b29
Change ID: puomrwxlkmpxwnnrtqoumzlsvqzkuxpn
Author   : Steve Klabnik <steve@steveklabnik.com> (2024-02-28 20:38:13)
Committer: Steve Klabnik <steve@steveklabnik.com> (2024-02-28 20:38:13)

    it's important to comment our code

Modified regular file src/main.rs:
        1: /// A "Hello, world!" program.
        2: 
   1    3: fn main() {
   2    4:     println!("Hello, world!");
   3    5: }

We get both IDs in full, both timestamps, the description, and the diff. jj show -r takes a revision in the same way. I tend to use jj show when I’m inspecting somebody else’s commit, and jj diff when I’m checking my own work.

Note the two timestamps. Author is when the change was first made; committer is when this version of it was written. Rewrite a commit — amend it, rebase it — and the second moves while the first stays put.

Comparing two versions of the same change

There’s a third command for a situation git doesn’t handle very well. Let’s say we’ve pushed a pull request, someone has reviewed it, and then we’ve rewritten the change. We may want to know what changed between those two versions, rather than what the change itself does.

$ jj interdiff --from 64fe802a --to @
Modified commit description:
   1     : feature v1
        1: feature v2
Modified regular file feat.txt:
   1     : one
        1: one and two

That’s jj interdiff, and it compares the effects of two commits rather than their contents, so the work underneath them doesn’t get in the way. The old commit ID comes from jj evolog, which we’ll meet properly later — it keeps every version of a change, so the thing the reviewer saw is still there to compare against.

I find it surprisingly useful to be able to answer “what did you change since my review?” precisely. This is another benefit of jj keeping old versions of our changes rather than overwriting them.

Before we move on, let’s put src/main.rs back the way it was. This goodbye was just something to point our new commands at; delete that line again, and jj st will confirm we’re back to a clean, empty change:

$ jj st
The working copy has no changes.
Working copy  (@) : ywnkulko 0b3dd9d2 (empty) (no description set)
Parent commit (@-): puomrwxl 7a096b8a it's important to comment our code

Don’t worry, we’ll write a real goodbye feature soon enough.