jj / How jj sparse edit works

How jj sparse edit works

jj sparse edit opens the workspace's sparse patterns in your text editor, one per line, and applies whatever list you save: files under new patterns are checked out, files under dropped ones are deleted from disk. Commits and the operation log are untouched.

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, which snapshots it as usual, and locks the working copy. It writes the current patterns to a temporary .jjsparse file, runs ui.editor, then parses each non-blank line that doesn't start with JJ: as a workspace-relative path. The result is sorted and de-duplicated. From there it is exactly jj sparse set: update the files, save the working-copy state under the same operation ID, and print the checkout stats.

The command is cmd_sparse_edit; the editing is edit_sparse. The update itself is the shared update_sparse_patterns_with, described in detail on the sparse set page.

1. Command-line surface

No arguments or flags (SparseEditArgs). The global --config ui.editor=… picks the editor for one run.

2. Configuration

KeyDefaultEffect
ui.editornano on Unix, Notepad on WindowsThe editor that opens the file, loaded before the working copy is locked (source, text_editor, default). $VISUAL or $EDITOR set it below your own config, and $JJ_EDITOR overrides it (source, override).

The patterns themselves live in the workspace's working-copy state, the sparse_patterns field of TreeState (see protobufs).

3. End-to-end flow

jj sparse edit: call flow cmd_sparse_edit loads the workspace through the shared lifecycle, which snapshots it, and loads the text editor. update_sparse_patterns_with locks the working copy and reads the patterns. edit_sparse writes them one per line to a temporary file ending in .jjsparse, runs the editor, and parses each remaining line as a repo-relative path. The list is sorted and de-duplicated. set_sparse_patterns checks out newly covered files and deletes uncovered ones; the working copy is saved under the old operation ID and the checkout stats are printed. Shared lifecycle: load workspace and repo, snapshot the working copy (CLI page) text_editor(); update_sparse_patterns_with: lock the working copy, read the patterns edit_sparse: write, then edit one pattern per line (root shown as ".") temp file editor-….jjsparse run ui.editor and wait parse the saved text drop "JJ:" lines; trim; skip blank each line → from_relative_path then sort and dedup (lines 45–46) set_sparse_patterns: check out files under added prefixes, delete files under removed ones the same step as jj sparse set finish(old operation ID): no operation; print "Added N files, modified 0 files, removed M files"

4. Core architecture touchpoints

StructureRead / writeHowExplained on
Sparse patterns (TreeState)read, writeShown in the editor, then replaced by the edited list (source).working-copy
Files on disk, file stateswriteCreated or deleted for prefixes whose inclusion changed.working-copy
Temporary filewritten, read, deletedIn the system temp directory, named editor-… with suffix .jjsparse; deleted only if editing succeeded (source).—
Working-copy commitreadSource of the checked-out files; never rewritten.trees
Operation loguntouchedSaved under the existing operation ID.operations

5. Transaction and operation

  • Transaction and operation: none. The working copy is locked while the editor is open and saved with old_operation_id() (source).
  • Lock held during editing: because the editor runs inside the closure, the working-copy lock is held until you close the editor.
  • On error (bad line, editor failure) nothing is written: the closure returns before set_sparse_patterns runs (source).
  • jj undo doesn't apply. Edit again or run jj sparse reset.

6. Algorithm and invariants

  1. Render. Each stored pattern is turned into a workspace-relative filesystem path and written on its own line. An invalid stored path doesn't block editing, but one that isn't UTF-8 is an internal error (source). Upstream's test shows the root pattern written as . (source).
  2. Edit. TextEditor::edit_str writes the temporary file, runs the editor, and reads it back; errors are labelled sparse patterns (source, edit_str).
  3. Parse. Lines starting with JJ: are dropped, the rest are trimmed and blank ones skipped, and each is parsed with RepoPathBuf::from_relative_path. The first failure aborts with Failed to parse sparse pattern: <line> (source).
  4. Normalize. Sort and remove duplicates (source).
  5. Apply. As in sparse set: check out added prefixes, delete removed ones, store the list.

Invariants: the saved list is sorted and has no duplicates. Saving an empty file leaves no patterns, so every tracked file is removed from disk. Commits never change.

7. Errors and edge cases

MessageWhenSource
Failed to parse sparse pattern: <line>, caused by Invalid component "." in repo-relative path "./file1" (or ..)A line isn't a clean workspace-relative path. . on its own is fine.edit.rs, test
Editor errors, labelled sparse patternsThe editor couldn't be run or exited with an error.edit.rs
Stored sparse path is not valid utf-8: … (internal error)A stored pattern can't be shown as text.edit.rs
Failed to update working copy paths (internal error)Writing or deleting files failed.mod.rs
This command must be able to update the working copy.Run with --at-op, --ignore-working-copy or --no-integrate-operation.cli_util.rs
Concurrent working copy operation. Try again.The working copy changed between loading and locking.cli_util.rs

8. Worked example

From upstream's test_sparse_manage_patterns, after jj sparse reset, so the patterns are . and file1, file2 and file3 are all on disk. The fake editor records what it was given and writes new content:

Two runs of jj sparse edit First run: the editor is given a single line, dot. It saves file1. jj removes file2 and file3 from disk: added 0, removed 2. Second run: the editor is given file1. It saves file3, file2, file3. The list becomes file2 and file3, sorted and de-duplicated; jj adds 2 files and removes 1. first jj sparse edit editor is given: . editor saves: file1 patterns: file1 on disk: file1 Added 0 files, modified 0 files, removed 2 files second jj sparse edit editor is given: file1 editor saves: file3, file2, file3 patterns: file2, file3 (sorted, deduped) on disk: file2, file3 Added 2 files, modified 0 files, removed 1 files

Upstream checks both outputs and the editor's input, and then that a saved line of ./file1 fails (source):

Error: Failed to parse sparse pattern: ./file1
Caused by: Invalid component "." in repo-relative path "./file1"