In short: jj takes the current operation. If that is itself a redo, it takes the operation that redo
restored. That operation must be an undo, or jj reports Nothing to redo. As with undo,
an operation from another workspace is refused. jj then restores the undo's parent, the state just
before the undo. If that parent is itself a redo, it restores what that redo restored instead. The new
operation is recorded as redo: restore to operation <id>.
The command is cmd_redo. It is the mirror image of
jj undo: same view restoration, same
cross-workspace guard, and the same trick of encoding the stack in operation descriptions. The
operations page compares both with
op restore and op revert. The two stacks are tracked separately, and redo
reads undo's prefix through UNDO_OP_DESC_PREFIX.
1. Command-line surface
| Flag | Type | Default | Meaning |
|---|---|---|---|
--allow-cross-workspace | bool | false | Redo an undo operation even if it was recorded in a different workspace (source). |
2. Configuration
jj redo reads no settings of its own. templates.op_summary formats the
Restored to operation: line (source), and the
working-copy settings govern the snapshot and checkout. Like undo, it always restores
both the repo portion and the remote-tracking portion of the view
(DEFAULT_REVERT_WHAT).
3. End-to-end flow
4. Core architecture touchpoints
| Structure | Read / write | How | Explained on |
|---|---|---|---|
| Current operation and its metadata | read | description, to tell whether it's a redo and which operation it restored, and whether the target is an undo. Also workspace_name and parents. | operations |
| Op store | read | load_operation for operations named in descriptions, and the restored operation's view. | operations |
View (all but the Git ref mirrors) | write | Replaced by the restored operation's heads, local bookmarks and tags, working-copy pointers, and remote views. | undo |
| Commits | none | Nothing is created or rewritten. The commits the undo hid become visible again. | commits |
| Operation | write | A new operation on top of the current one. | operations |
Working copy (TreeState) | read, then maybe write | Snapshotted at the start, then the restored @ is checked out. | working-copy |
5. Transaction and operation
- Transaction: one, started after the checks (source).
- Operation description:
redo: restore to operation <full hex id>(REDO_OP_DESC_PREFIX, source). Like undo's, this text is how the next redo finds its place. - Commits created or rewritten: none.
-
Working copy: as with undo, the workspace is loaded with a snapshot. Unrecorded
edits made after an undo are committed as a snapshot operation first, and that becomes the current
operation. It isn't an undo, so
jj redothen has nothing to redo. Making any new operation after an undo ends the redo chain. -
jj undoafter a redo treats the redo as an ordinary operation and restores its parent. A followingjj redorestores past that redo, which is the "adjacent redo stacks" case in the example below.
6. Algorithm and invariants
The redo stack mirrors the undo stack, with two substitutions (source comment):
-
What to redo. If the current operation is a redo, the next candidate is the
operation it restored (source). Only undo operations can be
redone. Anything else, a regular operation or a chain that leads to one, ends with
Nothing to redo(source). -
What to restore. The undo's parent, which must be unique. A second parent would be
an internal error, since undo operations always have exactly one. If that parent is itself a redo,
jj restores what it restored, so repeated
jj undo; jj redonever builds a linked list of redos (source).
Note that redo checks for the undo prefix, not for an operation recorded by jj undo as
such: an operation's identity as an undo is purely its description. jj op revert writes
revert operation <id> (source), so
jj redo right after it reports Nothing to redo.
Invariants: redo only ever reverses an undo. It never moves past the newest non-undo state. It appends one operation and deletes nothing. Its result is the exact view the undo replaced, apart from the Git ref mirrors, which keep their current values.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
Nothing to redo | The current operation, after following redos, isn't an undo. For example after a regular command, or after redoing everything (upstream test). | redo.rs |
Refusing to redo operation <id> because it was performed in workspace <name>, hint about --allow-cross-workspace or jj op revert | The undo being redone was recorded in another workspace (upstream test). | redo.rs |
internal error Undo operation should have a single parent | The undo has zero parents or several. | redo.rs |
internal error Failed to parse ID of target operation in redo-stack (and … target operation's parent …) | A description has the redo prefix but no valid hex ID. | redo.rs, redo.rs |
8. Worked example
As on the undo page, this is drawn as an
operation log, because redo changes views, not commits. It is upstream's
test_jump_over_old_redo_stack. Operations A–D each write their
letter to a file named state, followed by three undos, two redos, an undo, and redos until
nothing is left. The test checks the file after every step.
The file reads C, B, A after the undos, then B,
C after two redos, B after the undo, and C, then
D after the final redos. Redo 3 shows the parent substitution: U4's parent is the redo
R2, so jj restores what R2 restored (U1) instead of R2 itself. Redo 4 shows target substitution:
the current operation R3 is a redo, so its target is the undo R3 restored, U1, and U1's parent D is
restored. The two adjacent redo stacks are jumped over.