jj / How jj sparse list works

How jj sparse list works

jj sparse list prints the workspace's sparse patterns, one per line: the path prefixes whose files are written to disk. It reads one field of the working-copy state and changes nothing.

On this page
  1. 1. Command-line surface
  2. 2. Configuration
  3. 3. End-to-end flow
  4. 4. Core architecture touchpoints
  5. 5. What it reads, and snapshotting
  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, asks the working copy for its patterns, and prints each one as a path relative to the workspace root. The root pattern, which covers every file, prints as .. An empty list prints nothing, and means no file of the working-copy commit is on disk.

The command is cmd_sparse_list, sixteen lines. The patterns it shows are changed by its siblings jj sparse set, jj sparse edit and jj sparse reset; the working-copy page explains what they control.

1. Command-line surface

No arguments or flags (SparseListArgs {}). The help text: "List the patterns that are currently present in the working copy", adding that a newly cloned or initialized repo has one pattern matching all files from the repo root, rendered as . (source).

2. Configuration

None of its own. There is no template and no sort or format option: each line is the pattern's path, nothing more (source). The command doesn't ask for a pager. The patterns themselves are workspace state, not configuration: the sparse_patterns field (3) of the TreeState message in .jj/working_copy/tree_state (see protobufs), so each workspace of a repo has its own list.

3. End-to-end flow

jj sparse list: call flow cmd_sparse_list loads the workspace through the shared lifecycle, which snapshots it. It calls sparse_patterns on the working copy. The local working copy loads its tree state on first use: it reads the tree_state file, decodes the TreeState message, and turns the prefixes of its sparse_patterns field into repo paths, or the root alone if the field is missing. Each pattern is converted to a path relative to the workspace root, with the root shown as a dot, and printed on its own line. Shared lifecycle: workspace_helper loads workspace and repo, snapshots (CLI page) working_copy().sparse_patterns() the WorkingCopy trait method tree_state(): load on first use LocalWorkingCopy, a once-only cell TreeState::load open .jj/working_copy/tree_state read: decode the TreeState message tree, file states, patterns, watchman clock sparse_patterns_from_proto: each prefix string becomes a RepoPathBuf no sparse_patterns field at all (very old working copy): [root] to_fs_path_unchecked("") components joined with the OS separator root (empty path) becomes "." writeln! to stdout one line per pattern, stored order empty list: no output

LocalWorkingCopy implements the trait method as self.tree_state()?.sparse_patterns(), which returns the vector TreeState keeps (source). The same vector backs the PrefixMatcher that decides which files are checked out and snapshotted.

4. Core architecture touchpoints

StructureRead / writeHowExplained on
Sparse patterns (TreeState)readThe only thing the command itself reads (source).working-copy
tree_state filereadDecoded whole, though only one field is shown (source).protobufs
Working-copy commit, repo viewloadedBy the shared lifecycle, for the snapshot; the command doesn't use them.cli
Files on disk, operation loguntouchedApart from a snapshot, which happens before the command runs.operations

5. What it reads, and snapshotting

  • No transaction, no lock, no operation of its own (source).
  • Snapshot: workspace_helper snapshots the working copy first and prints the snapshot stats (source). A snapshot never changes the patterns, so it can't change the output.
  • Per workspace. The list belongs to the workspace you run in. A new workspace gets the current one's list when created with jj workspace add --sparse-patterns copy, which reads it through the same trait method (source; see workspace add).
  • jj undo has nothing to revert, except the snapshot if one was taken. Pattern changes aren't operations either, so jj undo doesn't bring back an earlier list.

6. Algorithm and invariants

The loop is a straight print (source). Each pattern is a RepoPathBuf, a workspace-relative path stored with / separators. It is rendered with to_fs_path_unchecked against an empty base, so the output is workspace-relative even when you run the command from a subdirectory, and uses the platform's separator. The root has no components, which would give an empty string, so it is rendered as .. The function's doc comment reserves it for display, which is all this is.

The order is the stored order. The writers keep it sorted: sparse set collects the new list in a HashSet and sorts it (source), and sparse edit sorts and removes duplicates (source). So the output is sorted and has no repeats, but list itself doesn't enforce either.

Invariants: nothing is written by the command. A pattern is a path prefix, not a glob: lib covers lib/a.rs and lib/x/b.rs. Prefixes can overlap (. with lib, say); the list shows them as stored.

7. Errors and edge cases

MessageWhenSource
(no output)The list is empty, for example after jj sparse set --clear. Not an error.test
.The default: the root pattern, which a new working copy starts with.local_working_copy.rs
Failed to read working copy state, caused by Reading tree state from <path> or Decoding tree state from <path>The tree_state file can't be read or isn't a valid message.load, errors
(prints .)No tree_state file: jj creates an empty state with the root pattern and saves it.load, init
(prints .)The state has no sparse_patterns field, which only a working copy older than the field lacks.local_working_copy.rs

A stored prefix that isn't a valid repo path is converted with unwrap(), so a corrupted pattern would crash rather than report an error (source). The writers only store valid paths, since sparse set parses its arguments as workspace-relative repo paths.

8. Worked example

From upstream's test_sparse_manage_patterns, in a repo with file1, file2 and file3. The later commands run from a subdirectory sub/, to show that patterns are workspace-relative both ways.

$ jj sparse list
.
$ jj sparse set --remove .
Added 0 files, modified 0 files, removed 3 files
$ jj sparse list
$ cd sub
$ jj sparse set --add file2 --add file3
Added 2 files, modified 0 files, removed 0 files
$ jj sparse list
file2
file3

Sources: the default and the empty list (source), the list from sub/ (source). The file2 above is the root's file2, not sub/file2. After a jj sparse edit that leaves file3, file2 and file3 in the editor, the list is again file2, file3: sorted, one line each (source).