In short: jj decides whether to edit (--edit, --no-edit, or
ui.movement.edit). In new-commit mode @ must be a head. The target is the
ancestors exactly offset generations above the start: @'s parents, or
@ when editing. With --conflict, it is the nearest conflicted ancestors
instead. One target is used, several prompt for a choice, and none is an error. One transaction then
checks out a new commit on the target (prev: A -> B) or edits it
(prev: A -> editing B).
cmd_prev calls the shared
move_to_commit with Direction::Prev. The
code, preconditions and outcomes are those of
jj next, except for how the target revset is
built. This page covers what differs and repeats the rest briefly.
1. Command-line surface
| Flag | Type | Default | Meaning |
|---|---|---|---|
OFFSET (positional) | unsigned integer | 1 | How many generations to move back (source). 0 is accepted in new-commit mode and targets @'s parents themselves, which starts a sibling of @ (upstream's test setup uses jj prev 0). |
-e, --edit | bool | off | Edit the ancestor, like jj edit (source). |
-n, --no-edit | bool | off | Create a new commit on the ancestor. Conflicts with --edit (source). |
--conflict | bool | off | Jump to the previous conflicted ancestor. Conflicts with OFFSET (source). |
2. Configuration
| Key | Default | Effect |
|---|---|---|
ui.movement.edit | false | Edit mode by default, overridden by --edit/--no-edit (default, source). |
revset-aliases."immutable_heads()" | (built-in) | In edit mode the target must be mutable. Going back often reaches immutable history, so prev --edit can fail where prev works. |
3. End-to-end flow
4. Core architecture touchpoints
| Structure | Read / write | How | Explained on |
|---|---|---|---|
View.wc_commit_ids, head_ids | read, write | As for jj next: @ is replaced by the target or a new child of it (source). | view |
| Revset engine | read | ancestors_at, heads and the HasConflict filter. | revsets |
| Commits | maybe write | One new empty commit in new-commit mode. The old @ is abandoned if it's discardable and unreferenced (source). | commits |
| Working copy | write | Updated to the new @. | working copy |
5. Transaction and operation
- Transaction: one, after the target is chosen.
- Operation description:
prev: <old @> -> <target>orprev: <old @> -> editing <target>, with short commit IDs (source). - Commits: at most one created (new-commit mode) and at most one abandoned (the old
@).
6. Algorithm and invariants
Unlike next, prev uses the same formula in both modes:
ancestors_at(start, offset) (source). Only the start
differs. With --edit, jj prev edits @'s parent. Without it,
jj prev puts the new @ on @'s grandparent, so that
@- ends up one commit further back. A merge has several parents, and following each one
can give several targets, which leads to the prompt
(test_prev_prompts_on_multiple_parents,
test_prev_choose_parent_by_id).
With --conflict the result is the heads of the conflicted ancestors: the nearest
conflicts going back. The source notes that roots() could be used to jump to the oldest
one instead, pending feedback (source).
Invariants: the same as jj next. New-commit mode needs a childless
@, edit mode never edits an immutable commit, and no existing commit is rewritten.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
No ancestor found N commit(s) back from the working copy parents(s) | New-commit mode, history isn't that deep. The hint lists each parent. "parents(s)" is spelled that way in the source. | movement_util.rs |
No ancestor found N commit(s) back from the working copy | The same, in edit mode. | movement_util.rs |
The working copy parent(s) have no ancestors with conflicts / The working copy has no ancestors with conflicts | --conflict found nothing. | movement_util.rs |
The working copy must not have any children | New-commit mode while @ has descendants. | movement_util.rs |
ambiguous prev commit, choose one to target: | Several targets, for example from a merge. Answer with an index or a unique change ID prefix; EOF cancels with an error. | movement_util.rs |
Commit … is immutable | Edit mode on an immutable ancestor. | movement_util.rs |
8. Worked example
From upstream's test_prev_simple. A linear stack
first → second → third, with the empty @
(zsuskuln) on top. Then jj prev runs:
Upstream's snapshot:
$ jj prev
Working copy (@) now at: royxmykx 539f176b (empty) (no description set)
Parent commit (@-) : rlvkpnrz 9439bf06 (empty) second
The start was @'s parent third, and one generation back is
second. With --edit instead, the start is @ itself, so jj would
edit third. test_prev_editing shows that from
fourth: Working copy (@) now at: kkmpptxz 7576de42 (empty) third.