In short: jj evaluates the target set and works through it parents-first. Each target's new parents
are its old parents, with any target parent replaced by that parent's own new parents. Only the
targets whose parents change must be mutable. A second map gives each target the set of targets that
a non-target child must now merge. In one transform_descendants pass, targets get their
new parents, non-target children of targets get the merged parent sets, and every commit whose
parents changed is rebased, which keeps its own diff. The operation is
parallelize N commits.
The command is cmd_parallelize. It uses the same
rewrite machinery as jj rebase: a
commit's tree is recomputed by merging its diff onto its new parents. How a rebased commit's tree is
computed is on the trees page.
1. Command-line surface
| Flag | Type | Default | Meaning |
|---|---|---|---|
REVSETS (positional), -r (hidden alias) | revsets, repeatable | none | The commits to parallelize. Both forms are unioned (source). |
The help text gives the canonical example, jj parallelize 1::2, and explains why
jj parallelize '1 | 3' is a no-op: the commit 2 between them isn't in the set,
so it still orders them (source).
2. Configuration
| Key | Default | Effect |
|---|---|---|
revset-aliases."immutable_heads()" | (built-in) | Only targets whose parents change are checked. A target whose parents are all outside the set keeps its parents, so it may be immutable (source, test). |
The command reads no other settings.
3. End-to-end flow
4. Core architecture touchpoints
| Structure | Read / write | How | Explained on |
|---|---|---|---|
| Commit parents | read, write | The whole point. Targets lose their target parents, and their non-target children gain several parents. | commits |
| Trees | write | rewriter.rebase() recomputes each rewritten commit's tree by applying its old diff to the new parents' merged tree. Removing a dependency can therefore create conflicts. | trees |
| Rewrite records | write | Every rewritten commit records its predecessor. Bookmarks and @ follow the rewritten commits. | transactions |
| Operation | write | One operation. | operations |
5. Transaction and operation
- Transaction: one, after the rewritability check (source).
- Operation description:
parallelize <N> commits, where N counts every target, including those left unchanged (source). - Commits rewritten: targets with a target parent, non-target children of targets, and all their descendants (rebased with new IDs).
- Commits unchanged: targets whose parents are all outside the set. In upstream's tests, target
1keeps its commit ID (test).
6. Algorithm and invariants
New parents for targets
Targets are processed parents-first (source). For each parent of
target T: if the parent is a target, T takes that parent's already computed new parents.
Otherwise T keeps the parent. In a chain 0 ← 1 ← 2 ← 3 with targets 1::3,
that gives 1 → [0], 2 → [0] and 3 → [0], so all three become
children of 0.
New parents for non-target children
A non-target commit that sat on a target used to see every target below it in its ancestry. To keep
those ancestors, new_child_parents maps each
target to itself plus everything its target parents mapped to. For the chain above, that maps
3 to {1, 2, 3}. A child of 3 therefore becomes a merge of
1, 2 and 3 (source). This
is why the help text says descendants outside the set "remain descendants".
Rebasing
Only commits whose parents changed are written (source). They
use rebase(), not reparent(), so each commit keeps the changes it
introduced, not its old snapshot. A target that depended on an earlier target's edits can therefore
end up conflicted.
Invariants: ancestry with commits outside the set is preserved in both directions. Ancestry among targets is removed. Every changed target is mutable.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
Commit <id> is immutable, with This operation would rewrite N immutable commits. | A target whose parents would change is immutable. | parallelize.rs, test |
Nothing changed. | No commit's parents changed, for example with one target, disconnected targets, or '1 | 3' as in the help. | cli_util.rs, help text |
| (conflicts after the rebase) | A target that depended on another target's changes. jj doesn't refuse this. The result just shows conflicts. | parallelize.rs |
8. Worked example
From upstream's test_parallelize_with_descendants_simple. A linear
stack 1 → 2 → 3 → 4 → 5 →
6 (@). Then jj parallelize 'subject(1)::subject(4)' runs:
Upstream's log after the command (template: commit ID, description, parents' descriptions):
@ 75ac07d7dedc 6 parents: 5
○ 39791a4c42c5 5 parents: 1 2 3 4
├─┬─┬─╮
│ │ │ ○ 823bf930aefb 4 parents:
│ │ ○ │ 3b6586259aa9 3 parents:
│ │ ├─╯
│ ○ │ dfd927ce07c0 2 parents:
│ ├─╯
○ │ 884fe9b9c656 1 parents:
├─╯
◆ 000000000000 parents:
1 keeps its ID because its parent (the root) is outside the set. 2,
3 and 4 now sit on the root. 5 was a non-target child of
4, and new_child_parents[4] = {1, 2, 3, 4}, so it becomes a four-way merge.
When a non-target child hangs off an interior target, it merges only the targets up to that point, as
in test_parallelize_where_interior_2c parents: 1 2).