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 HistoryEvery 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:
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 FearMade 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)
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 PointUndo 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.
# 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...
# 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.
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 |
# Rebased onto wrong commit:
$ jj rebase -d wrong-thing
# Immediate fix:
$ jj undo
# 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.
# Squashed into the wrong commit:
$ jj squash --into wrong-ancestor
# Undo:
$ jj undo
# Changes are back in the original commit.
# Accidentally deleted a bookmark:
$ jj bookmark delete important-branch
# Undo restores the bookmark and everything it pointed to:
$ jj undo
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.
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 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.
jj op log show that git reflog doesn't?jj undo. No arguments needed. It reverses the last operation and restores the entire repo to its previous state. The simplest possible recovery command.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.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.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.jj Documentation — Operation Log — official reference for the operation log, undo, restore, and advanced operation management.