jj / How jj operation integrate works

How jj operation integrate works

jj op integrate X makes an operation that exists in the store, but isn't reachable from the op heads, part of the operation log again. It adds X as an op head and lets jj's usual concurrent-operation handling merge it with whatever else is there.

On this page
  1. 1. Command-line surface
  2. 2. Configuration
  3. 3. End-to-end flow
  4. 4. Core architecture touchpoints
  5. 5. Transaction and operation
  6. 6. Algorithm and invariants
  7. 7. Errors and edge cases
  8. 8. Worked example

In short: jj loads the workspace without snapshotting and resolves X. It calls update_op_heads(parents of X → X), so X becomes a head next to the current one. Then resolve_op_heads runs: if X turns out to be an ancestor of another head, it is simply dropped again and nothing happens. Otherwise the heads are merged into a new operation, reconcile divergent operations, and jj prints The specified operation has been integrated with other existing operations.

The command is cmd_op_integrate. Its help explains when that's needed: operations are integrated automatically, but --no-integrate-operation or an internal error can leave one out (source). jj points you here itself when the working copy's operation turns out to be a sibling of the loaded one. Op heads and the merge of concurrent operations are described on the operations page.

1. Command-line surface

ArgumentTypeDefaultMeaning
OPERATION (positional)operation expressionrequiredThe operation to integrate (source).

OperationIntegrateArgs

2. Configuration

None. The workspace is loaded with workspace_helper_no_snapshot, so no snapshot of the working copy is taken first (source, helper). The global --no-integrate-operation is the flag that creates operations needing this command.

3. End-to-end flow

jj op integrate: call flow cmd_op_integrate loads the workspace without snapshotting and resolves X. update_op_heads removes X's parents from the op heads and adds X. resolve_op_heads reads the heads: with one head, done. With several, under a lock it drops heads that are ancestors of others; if one remains it is used as is. Otherwise merge_operations merges them into a new operation, reconcile divergent operations, and jj prints that the operation has been integrated. workspace_helper_no_snapshot; resolve_single_op(OPERATION) → X op_heads_store.update_op_heads(X's parents, X): X is now a head resolve_op_heads: one head → done; several → lock and re-read drop heads that are ancestors X already in the log → it's an ancestor of the current head: removed, no new operation, no output still several heads merge_operations → new operation "reconcile divergent operations" print "… has been integrated …"

4. Core architecture touchpoints

StructureRead / writeHowExplained on
Op heads storeupdatedX added, its parents removed; then the merged operation replaces the heads it merged (update_op_heads, source).operations
Operation and view storeswrittenThe merge writes a new view and operation whose parents are the merged heads.operations
Commitsmaybe rewrittenIf one side rewrote commits that the other built on, the merge rebases them and prints Rebased N descendant commits onto commits rewritten by other operation. (source).operations
Working copynot touchedNo snapshot and no checkout. The next command reloads it against the new head.working-copy

5. Transaction and operation

  • No transaction of the command's own. The only operation written is the merge, created inside resolve_op_heads by RepoLoader::merge_operations (source).
  • Operation description: reconcile divergent operations, recorded with this workspace's name and the command's arguments (source).
  • Already integrated: nothing is written and nothing printed (test).
  • Undoing it: jj undo refuses a merge operation; jj op restore to one of the merged operations works.

6. Algorithm and invariants

  1. update_op_heads(old, new) removes the old heads and adds the new one. Passing X's parents as "old" means that if a parent was a head, X replaces it, and otherwise X is simply added next to the existing heads (source).
  2. resolve_op_heads reads the heads. With several, it locks, re-reads, and drops heads that are ancestors of other heads. One left: it's used as is, and the dropped ones are removed from the op heads. Still several: they're sorted by end time and handed to the resolver.
  3. The resolver calls merge_operations, which merges the repos at those operations, using their closest common ancestors as bases, and writes the result as one operation. The op heads are then replaced by it.

Invariants: nothing is removed from the operation store. Integrating twice is harmless. After the command there is exactly one op head, unless another process raced it.

7. Errors and edge cases

MessageWhenSource
The specified operation has been integrated with other existing operations.A merge operation was created.integrate.rs
(no output)X is already in the operation log.test
Internal error: The repo was loaded at operation <a>, which seems to be a sibling of the working copy's operation <b>, hint Run `jj op integrate <b>` …Printed by other commands; the reason to run this one.command_error.rs
Rebased <n> descendant commits onto commits rewritten by other operation.The merge had to rebase commits.cli_util.rs
Operation-resolution errorsX doesn't resolve to one operation.cli_util.rs

8. Worked example

From upstream's test_integrate_sibling_operation. The test runs jj new -m=first, then removes that operation from the op heads by hand, and runs jj new -m=second --ignore-working-copy, which builds on the older operation. The working copy still records the first one, so jj op log fails with the sibling error and its hint. Running jj op integrate 4bffc36765bd fixes it:

jj op integrate on a sibling operation Before: the op heads hold only 63a847e2 (new -m=second), a child of f63ee16f (add workspace). The operation 4bffc367 (new -m=first) exists in the store, also a child of f63ee16f, but isn't a head. After jj op integrate 4bffc367: both become heads and are merged into f28c8b57, reconcile divergent operations, with both as parents. before after jj op integrate 4bffc367 f63ee16f add workspace 4bffc367 first (not a head) 63a847e2 second (op head) f63ee16f add workspace 4bffc367 first 63a847e2 second f28c8b57 reconcile divergent operations

Upstream's output and the new top of the operation log (source):

The specified operation has been integrated with other existing operations.

@    f28c8b5750f5 … default@ …
├─╮  reconcile divergent operations
│ │  args: jj op integrate 4bffc36765bd…