In short: jj loads the workspace as usual and resolves X. It builds a new View from X's view
and the current one: heads, local bookmarks and tags, and working-copy commits come from X; remote
bookmarks and tags come from X too unless --what leaves them out; the Git ref mirrors stay
current. It installs that view in one transaction, prints Restored to operation: …, and
finishes as restore to operation <id>, which checks out the restored @.
The command is cmd_op_restore. It shares its view
logic with jj undo and
jj op revert. The operation log
itself is described on the operations page.
1. Command-line surface
| Argument | Type | Default | Meaning |
|---|---|---|---|
OPERATION (positional) | operation expression | required | The operation to restore to, such as an ID prefix or @-. Its help suggests jj --at-op=<id> log to preview the state first (source). |
--what | repo | remote-tracking, repeatable | both | Which parts to restore. Experimental (source, values). |
2. Configuration
No restore-specific keys. templates.op_summary formats the
Restored to operation: line (source). The global
--at-op changes which operation the command starts from, not the target.
3. End-to-end flow
4. Core architecture touchpoints
| Structure | Read / write | How | Explained on |
|---|---|---|---|
Target operation's View | read | Loaded with target_op.view() (source). | view |
Current View | replaced | By the combined view, through MutableRepo::set_view (source). | view |
| Commits | none written | Commits that were hidden become visible again simply because the restored view's heads point at them. | commits |
| Operation log | append | One new operation whose parent is the current one. | operations |
| Working copy | maybe written | Updated to the restored @. If the workspace isn't in the restored view, it's left alone with a warning. | working-copy |
5. Transaction and operation
- Transaction: one (source).
- Operation description:
restore to operation <full hex id of X>(source). - No change: restoring to the current operation prints the summary and then
Nothing changed.; nothing is recorded (test). - Undoing it:
jj undo, orjj op restoreto the operation before the restore. - Before it: the load's snapshot may record a
snapshot working copyoperation, so unrecorded edits are kept in the log even though the restore moves away from them.
6. Algorithm and invariants
view_with_desired_portions_restored picks each field
of the new View:
| Field | Taken from |
|---|---|
head_ids, local_bookmarks, local_tags, wc_commit_ids | X if --what includes repo, else current |
remote_views (remote bookmarks and tags) | X if --what includes remote-tracking, else current |
git_refs, git_heads | always current |
The remote-tracking help notes not to restore those "if you'd like to push after the undo"
(source): restoring them makes jj forget what a push or fetch told
it about the remote. The view is installed as-is with set_view, which marks the heads as
not yet normalized (source).
Invariants: append-only: no operation or commit is deleted. Only the view changes. The Git ref mirrors are never rolled back, so the next import or export still compares against what Git actually has.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
Warning: The current workspace '<name>' no longer exists after this operation. The working copy was left untouched., hint to restore to an operation that contains it | X predates the workspace, e.g. the root operation. | cli_util.rs, test |
Nothing changed. | X's view equals the current one. | cli_util.rs |
| Operation-resolution errors (no such operation, ambiguous prefix) | X can't be resolved to exactly one operation. | cli_util.rs |
clap: missing <OPERATION> | No argument; unlike op revert, there's no default. | restore.rs |
8. Worked example
From upstream's test_git_push_revert, which uses
jj op restore to take back a push. main was pushed at AA, then
described BB and pushed again; the test restores to the operation before that second push:
Upstream's bookmark listing after the restore (source):
main: qpvuntsm d9a9f6a0 (empty) BB
@origin (ahead by 1 commits, behind by 1 commits): qpvuntsm/1 3a44d6c5 (hidden) (empty) AA
A later jj git fetch, after describing main as CC, ends with
main conflicted. The test's own TODO explains why: the restore made jj forget the remote was
already at BB, so the fetch looked like a remote update. --what repo avoids that by
keeping the current remote-tracking bookmarks (test_git_push_revert_repo_only).