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

Watching a change evolve with jj evolog

We’ve said a few times now that a change ID is stable while the commit ID underneath it changes. Every time you describe a change, edit its files, squash something into it, or let it get automatically rebased, you get a new commit ID and the same change ID.

jj evolog shows you that history. Here’s a change that’s been through a few rounds:

$ jj evolog -r usvzmupx
○  usvzmupx steve@steveklabnik.com 2024-03-20 10:02:11 c39e9f3f
│  add a better feature
│  -- operation c4e5378aa59c snapshot working copy
○  usvzmupx/1 steve@steveklabnik.com 2024-03-20 10:01:58 0ea8e5fc (hidden)
│  add a better feature
│  -- operation 3286efef174e describe commit e7b530dc15e0
○  usvzmupx/2 steve@steveklabnik.com 2024-03-20 10:01:58 e7b530dc (hidden)
│  add a feature
│  -- operation 685d3932d80a snapshot working copy
○  usvzmupx/3 steve@steveklabnik.com 2024-03-20 10:01:40 6d256008 (hidden)
│  add a feature
│  -- operation 91adfe1a92ae describe commit 39f08e04f359
○  usvzmupx/4 steve@steveklabnik.com 2024-03-20 10:01:40 39f08e04 (hidden)
│  (no description set)
│  -- operation cbf76d26122b snapshot working copy
○  usvzmupx/5 steve@steveklabnik.com 2024-03-20 10:01:22 6161a9a2 (hidden)
   (empty) (no description set)
   -- operation 4261b1b2ee68 add workspace 'default'

If we read it from bottom to top, we can watch the change evolve. It starts as the empty commit jj git init made. It gets some content. It gets described as “add a feature”. More content. Then a better description. Every one of those steps left a commit behind, and they’re all still here.

That usvzmupx/1 notation is how jj refers to older versions of a change: the change ID, then which step back you mean. And (hidden) means what it did in jj op show — the commit exists, it’s just not part of the visible history any more.

Each entry also names the operation that produced it, on the -- operation line, which is the bridge back to the last chapter. If you spot the version you want here, you can find the operation that came after it and jj op restore to just before the step you regret.

If we pass -p, we get the diff at each step. This is the version I usually reach for, because it shows us what the change contained over time, not just what it was called.

When two versions stay visible

Normally, only the newest entry in an evolog is visible; everything older is (hidden). But hidden commits can come back. Put a bookmark on one, build a new commit on top of one, or fetch history that descends from one, and it becomes visible again — alongside its newer self. At that point a single change ID names two visible commits, and jj marks both (divergent).

That situation deserves a proper walkthrough of its own, and it gets one in the next chapter. For now, just file away the connection: the old versions the evolog shows you are real commits, and “divergent” is what it’s called when one of them escapes.

evolog versus op log

These two commands can be easy to confuse, so let’s put the distinction in one line each.

jj op log is a history of your repository: what you did, in order, to everything.

jj evolog is a history of one change: every version that one logical piece of work has ever had.

They’re two different views of the same data. When we know something went wrong but not which change it affected, we can use jj op log. When we know which change went wrong but not when, we can use jj evolog.

One last note: jj evolog used to be called jj obslog, for “obsolete log”. That name still works if you type it, and you’ll see it in older blog posts and Discord messages, but evolog — for “evolution” — is what it’s called now, and it’s a much better description of what you’re looking at.