The Operation Log and Undo

Lesson 6 · jj for Git Users · ~8 minutes

Every mutation to a jj repository is recorded as an operation. The operation log is an immutable, human-readable history of everything you've done — and it gives you a one-command undo for any operation. This is the safety net that makes jj's fearless history rewriting possible.

If git's reflog is a detective's notebook (cryptic, per-ref, expires after 90 days), jj's operation log is a full flight recorder (atomic, human-readable, covers everything, never expires).

jj op log — View Operation History

Every time you run a jj command that mutates the repo, it creates an operation entry. View the history with jj op log:

$ jj op log
@  4a8f2c1d2e3f user@host 2024-03-15 14:23:01 UTC
│  rebase commit sqpuoqvx and descendants
○  8b3e4d5f6a7b user@host 2024-03-15 14:22:45 UTC
│  edit commit tpylkqrs
○  c9d0e1f2a3b4 user@host 2024-03-15 14:20:12 UTC
│  new empty commit
○  1e2f3a4b5c6d user@host 2024-03-15 14:19:58 UTC
│  describe commit sqpuoqvx
○  5f6a7b8c9d0e user@host 2024-03-15 14:15:30 UTC
│  snapshot working copy
○  a1b2c3d4e5f6 user@host 2024-03-15 14:10:00 UTC
   git fetch

Each entry shows:

Operations are atomic

Each operation captures the entire repo state — all commits, all bookmarks, all refs at once. This is fundamentally different from git's reflog, which tracks individual refs separately. One jj operation = one consistent snapshot of everything.

jj undo — One Command, No Fear

Made a mistake? Undo the last operation with a single command:

# You just did a bad rebase:
$ jj rebase -d wrong-commit
# Oops. Undo it:
$ jj undo
# Repository is back to exactly how it was before the rebase.

That's it. No git reflog + git reset --hard dance. No finding the right SHA. No worrying about which ref to reset. One command restores the entire repo state.

# Undo works for any operation:
$ jj squash --into wrong-commit    # Mistake!
$ jj undo                           # Fixed.

$ jj git push                       # Pushed too early!
$ jj undo                           # Local state restored.
# (Note: the remote push already happened — undo restores local state only)
Undo restores local state only

jj undo reverses the local repo state. If the undone operation was a push, the commits are already on the remote. You'd need to force-push again to update the remote. For local-only operations (rebase, edit, squash, split), undo is a complete reversal.

jj op restore — Jump to Any Point

Undo goes back one step. jj op restore lets you jump to any point in your operation history — like time travel for your entire repository:

# View operation history:
$ jj op log
@  4a8f2c1d  rebase commit sqpuoqvx and descendants
○  8b3e4d5f  edit commit tpylkqrs
○  c9d0e1f2  new empty commit
○  1e2f3a4b  describe commit sqpuoqvx
○  5f6a7b8c  snapshot working copy

# Restore to how things were 3 operations ago:
$ jj op restore 1e2f3a4b
# Repo is now exactly as it was at that point in time.

This is far more powerful than git's reflog because it restores everything — all commits, all bookmarks, all working copy state — as a single atomic snapshot.

Git: recover from a bad rebase

# Something went wrong 3 operations ago...
$ git reflog
# Find the right SHA among cryptic entries:
# abc1234 HEAD@{0}: rebase (continue)
# def5678 HEAD@{1}: rebase (pick)
# 789abcd HEAD@{2}: rebase (start)
# fab4321 HEAD@{3}: commit: my work  ← this one!

$ git reset --hard fab4321
# ⚠️ Only resets HEAD — other branches unchanged
# ⚠️ Stash might be lost
# ⚠️ Hope you picked the right SHA...

jj: recover from a bad rebase

# Something went wrong...
$ jj op log
# Human-readable descriptions:
# 4a8f  rebase commit and descendants
# 8b3e  edit commit tpylkqrs
# c9d0  new empty commit  ← before the mess

$ jj op restore c9d0
# Done. Entire repo restored atomically.
# All commits, all bookmarks, everything.

Why Better Than Git Reflog

The operation log solves every pain point of git's reflog:

Feature git reflog jj op log
Scope Per-ref (HEAD, each branch separately) Entire repo as one atomic snapshot
Readability Cryptic (HEAD@{3}: rebase (pick): abc123) Human-readable (rebase commit and descendants)
Expiration Expires after 90 days (gc.reflogExpire) Never expires — permanent history
Restore Must find the right SHA + reset --hard jj op restore <id> — one command
Coverage Misses some operations (worktree, stash internals) Every repo mutation recorded
Multi-ref Each ref has its own reflog — must piece together state One operation = one consistent state of all refs

Use Cases

Bad rebase recovery

# Rebased onto wrong commit:
$ jj rebase -d wrong-thing
# Immediate fix:
$ jj undo

Experimentation without risk

# Try a risky refactor:
$ jj op log | head -1   # Note current operation ID: abc123
# ... try things, edit history, rebase, squash ...
# Didn't work out? Restore:
$ jj op restore abc123
# Back to exactly where you started.

Accidental squash recovery

# Squashed into the wrong commit:
$ jj squash --into wrong-ancestor
# Undo:
$ jj undo
# Changes are back in the original commit.

Recovering after accidental bookmark deletion

# Accidentally deleted a bookmark:
$ jj bookmark delete important-branch
# Undo restores the bookmark and everything it pointed to:
$ jj undo
The safety net philosophy

The operation log is what makes jj's aggressive history rewriting safe. You can jj edit, jj split, jj squash, and jj rebase fearlessly because every operation is reversible. If anything goes wrong, jj undo or jj op restore gets you back instantly. This is why jj users rewrite history constantly — the cost of mistakes is near zero.

Advanced: Operation Diffs

You can see what changed between operations using jj op diff:

# See what the last operation changed:
$ jj op diff

# See what a specific operation changed:
$ jj op diff --from 8b3e4d5f --to 4a8f2c1d

This shows exactly which commits were added, removed, or modified — useful for understanding what happened before deciding whether to undo.

The Mental Model

Git: commits ← reflog (per-ref, cryptic, expires) Working state NOT fully captured. jj: commits ← operations ← operation log Each operation = full atomic snapshot. Never expires. Human-readable. One-command restore. Think of it like: git reflog = browser history (per-tab, messy, auto-deletes) jj op log = Time Machine backup (whole system, browsable, permanent)

The operation log doesn't just record what happened — it enables a fundamentally different relationship with your version control tool. When any mistake is instantly reversible, you stop being careful and start being creative.

Check Your Understanding

What does jj op log show that git reflog doesn't?
Correct! Each jj operation is an atomic snapshot of the entire repo state — all commits, all bookmarks, all refs together. Git's reflog tracks each ref separately, so you have to piece together what the repo looked like at a given point by cross-referencing multiple reflogs.
Not quite. The key difference is atomicity: each jj operation captures the entire repo state (all commits, bookmarks, and refs) as one snapshot. Git's reflog is per-ref — HEAD has its own reflog, each branch has its own. You can't easily reconstruct the full repo state at a point in time from git's reflog.
How do you undo the last operation in jj?
Correct! Just jj undo. No arguments needed. It reverses the last operation and restores the entire repo to its previous state. The simplest possible recovery command.
Not quite. The command is simply jj undo — no arguments, no SHAs, no ref syntax. It reverses the most recent operation atomically. The git-style commands (reset --hard, revert) don't exist in jj.
How do you restore the repo to exactly how it was 5 operations ago?
Correct! Use jj op log to find the operation ID, then jj op restore <id>. This atomically restores the entire repo — all commits, bookmarks, and state — to exactly how it was at that point. No partial restoration, no ref-by-ref reconstruction.
Not quite. The correct approach: look at jj op log to find the operation ID you want to restore to, then run jj op restore <id>. This restores the full repo state atomically. Running jj undo multiple times would work but isn't the intended pattern for jumping back multiple steps.
Primary Source

jj Documentation — Operation Log — official reference for the operation log, undo, restore, and advanced operation management.

Questions? The operation log is the foundation of jj's safety model. If you want to explore specific recovery scenarios, understand how op log interacts with remote pushes, or compare it to a git workflow you currently use — ask me.
← Prev Coming soon