jj
(jujutsu) is a newish git-compatible version control system that has some fresh ideas and a pretty great CLI UX (compared to git
). I had a moment recently where I hadn't yet committed my changes and while attempting to format the code I inadvertently made my diff way larger than it should have been.
Here's what happened:
- I made some changes that I was quite happy with and did a quick
jj diff
/jj status
to make sure there was nothing I was accidentally including in the change.
❯ : jj status
Working copy changes:
M Cargo.toml
M src/de.rs
M src/lib.rs
M src/ser.rs
M src/value.rs
M src/value_impls.rs
Working copy (@) : zxsrvopz 43a4bc7d master | de: very rough PoC of refcounted data
Parent commit (@-): xlzooroo 9b8d55a1 core: update quickcheck + rand and tests (tests failing)
-
I noticed that the changes were not well-formatted, so I ran
cargo fmt
-
I ran
jj status
and had an oh shit moment when I realized way more files were changed from thecargo fmt
than I thought would be.
❯ : jj status
Working copy changes:
M Cargo.toml
M src/consts.rs
M src/de.rs
M src/error.rs
M src/lib.rs
M src/ser.rs
M src/value.rs
M src/value_impls.rs
M test/arby.rs
M test/mod.rs
Working copy (@) : zxsrvopz 69322ff0 master | de: very rough PoC of refcounted data
Parent commit (@-): xlzooroo 9b8d55a1 core: update quickcheck + rand and tests (tests failing)
So now I have a problem: how do I undo the cargo fmt
step?
Enter jj evolog
One of jj
's commands is evolog
, which "shows how a change has evolved over time". jj
takes a snapshot of the working copy every time a command is run, so when I ran jj diff
to check my work, it recorded a snapshot of my working copy from the last time I ran a jj
command.
Running jj evolog
you'll see something like:
❯ : jj evolog
○ mmxmynwt hidden example@example.com 2025-10-03 18:08:05 7aa68914
│ (no description set)
│ -- operation f606802e1b09 (2025-10-03 18:08:05) snapshot working copy
○ mmxmynwt hidden example@example.com 2025-10-03 18:07:58 4970219f
(empty) (no description set)
-- operation bb8323da564f (2025-10-03 18:07:58) new empty commit
Pretending that there's more than the empty commit and snapshot working copy operations, this on its own is not super helpful in identifying what happened in each operation.
Running jj evolog -p
shows more details:
❯ : jj evolog -p
@ zxsrvopz example@example.com 2025-10-03 18:07:03 master 69322ff0
│ de: very rough PoC of refcounted data
│ -- operation 0df245508eb8 (2025-10-03 18:07:03) snapshot working copy
│ src/consts.rs --- Rust
│ No syntactic changes.
│
│ src/error.rs --- 1/3 --- Rust
│ 6 6
│ 7 //! Error objects and codes 7 //! Error objects and codes
│ 8 8
│ 9 use std::fmt; 9 use serde::{de, ser};
│ 10 use std::io; 10 use std::error;
│ 11 use std::error; 11 use std::fmt;
...skipping...
○ zxsrvopz hidden example@example.com 2025-10-02 19:31:08 43a4bc7d
de: very rough PoC of refcounted data
Cargo.toml --- TOML
10 edition = "2024"
11
12 [dependencies]
13 serde = { version = "1.0.104", features = ["rc"] }
14 byteorder = "1.3.2"
15 num-bigint = "0.4.0"
16 num-traits = "0.2.10"
src/lib.rs --- Rust
68 68 //!
69 69 //! The minimum supported version of the toolchain is 1.41.1.
70 ..
71 .. #![cfg_attr(feature = "unstable", feature(test))]
72 70
73 71 pub use self::ser::{
74 72 Serializer,
/* The rest */
There are two important lines here:
@ zxsrvopz example@example.com 2025-10-03 18:07:03 master 69322ff0
And
○ zxsrvopz hidden example@example.com 2025-10-02 19:31:08 43a4bc7d
zxsrvopz
is the change ID (which is the same for both lines) while 43a4bc7d
and 69322ff0
are the unique commit IDs. In the above output I just searched in the output for zx
to jump from the first commit to the second and saw that the second is what I wanted to restore to.
So now I can jj edit 43a4bc7d
to jump back to that specific operation and I see the following output:
❯ : jj edit 43a4bc7d
Working copy (@) now at: zxsrvopz?? 43a4bc7d de: very rough PoC of refcounted data
Parent commit (@-) : xlzooroo 9b8d55a1 core: update quickcheck + rand and tests (tests failing)
Added 0 files, modified 8 files, removed 0 files
See the zxsrvopz??
change ID? If the above was appropriately highlighted you'd see it in red, but jj
now is telling me that I have a conflicted change by placing that text in red and showing the ??
after the change ID. If I run jj log
I see my history has branched and I have two changes with the same ID:
❯ : jj log
@ zxsrvopz?? example@example.com 2025-10-02 19:31:08 43a4bc7d
│ de: very rough PoC of refcounted data
│ ○ zxsrvopz?? example@example.com 2025-10-03 18:07:03 master 69322ff0
├─╯ de: very rough PoC of refcounted data
○ xlzooroo example@example.com 2025-10-02 17:00:07 git_head() 9b8d55a1
│ core: update quickcheck + rand and tests (tests failing)
The change starting with @
is the revision I'm currently on while the other with a ○
is the forked revision. I can now resolve this conflict by simply abandoning the other change:
> : jj abandon 69322ff0
Abandoned 1 commits:
zxsrvopz?? 69322ff0 master | de: very rough PoC of refcounted data
Deleted bookmarks: master
❯ : jj log
@ zxsrvopz example@example.com 2025-10-02 19:31:08 43a4bc7d
│ de: very rough PoC of refcounted data
○ xlzooroo example@example.com 2025-10-02 17:00:07 git_head() 9b8d55a1
│ core: update quickcheck + rand and tests (tests failing)
And we're fixed!
I've reproduced what happened here in a short shell session which will have better highlighting and more info (note: clicking will take you to asciinema.org
):