In short: jj loads the workspace and snapshots it, keeping the snapshot's statistics. If the workspace
has a working-copy commit, it prints a summary diff of @ against its merged parents (with
renames and copies), the untracked paths that snapshot found, collapsed to directories where it can,
and one line each for @ and its parents. If @ is conflicted it lists the
conflicted paths and may suggest where to start resolving. Finally it lists conflicted local and remote
bookmarks. It writes nothing beyond what the snapshot itself records.
The command is cmd_status. The help text: "Show
high-level repo status", covering "the working copy commit and its parents, and a summary of the
changes in the working copy (compared to the merged parents)", conflicts in the working copy, and
conflicted bookmarks (source). The st alias is a
built-in default (source).
1. Command-line surface
| Argument | Type | Default | Meaning |
|---|---|---|---|
FILESETS (positional, repeatable) | fileset | all paths | "Restrict the status display to these paths" (source, source). It filters the changes, the untracked paths and the conflicted paths; the @/@- lines and the bookmark warnings are always printed. |
That is the whole surface: there is no -r and no format flag. The help text points to
jj diff --summary -r <rev> for another revision (source);
see jj diff.
Global flags such as --at-op and --ignore-working-copy are on the CLI page.
2. Configuration
| Key | Default | Effect |
|---|---|---|
snapshot.auto-track | all() | New files outside this fileset are left untracked and listed under "Untracked paths" (source, source, source). |
snapshot.max-new-file-size | 1MiB | New files larger than this are left untracked, warned about on stderr, and listed under "Untracked paths". 0 means no limit (source, source). |
templates.commit_summary | format_commit_summary_with_refs(…) | The template for the @ and @- lines (source, source, source). |
hints.resolving-conflicts | true | Whether to print the "To resolve the conflicts…" hint (source, source). |
revset-aliases."immutable_heads()" | trunk() | tags() | untracked_remote_bookmarks() | untracked_remote_tags() | Conflicted ancestors that are immutable are left out of the hint (source, source). |
colors."diff untracked" | magenta | Colour of the ? lines (source, source). |
ui.pager, ui.paginate | less -FRXK, auto | Output goes through the pager (source, source). |
3. End-to-end flow
4. Core architecture touchpoints
| Structure | Read / write | How | Explained on |
|---|---|---|---|
| Working copy | read (snapshot) | The snapshot both updates @ and reports the new files it left untracked, with a reason for each (source). | working copy |
| Commits | read | @ and its parents, loaded by id (source, source). | commits |
| Trees | read | @'s tree is diffed against the parents' merged tree; the untracked-path walk loads tracked subtrees. | trees |
| Copy records | read | Collected from each parent to @, filtered by the matcher, so renames and copies show as R and C (source, source). | trees |
| Conflicts | read | A commit is conflicted when its tree ids are not resolved (source); the conflicted paths come from conflicts_matching (source). | conflicts |
| Revset engine | read | Only for the hint: conflicted, mutable ancestors of @, then their roots (source, source). | revsets |
| View | read | Local and remote bookmarks whose target is not resolved (source). | view |
| Operation log | written only by the snapshot | If the files changed, the snapshot records a snapshot working copy operation; status starts no transaction of its own. | operations |
5. What it reads, and snapshotting
- Snapshot: yes, and the statistics matter.
statuscallsworkspace_helper_with_statsrather thanworkspace_helper(source, source), because the "Untracked paths" section is built fromSnapshotStats.untracked_paths. That list is not stored anywhere; it exists only for the snapshot just taken (source). - Without a snapshot: with
--ignore-working-copy, or at an--at-opthat is not the head operation, no snapshot runs and the statistics are empty (source, source), so no untracked paths are listed, and the changes shown are those recorded at the last snapshot. - Ignored files are not "untracked": a new file matching
.gitignoreis skipped silently and never appears (source, test). - Output: snapshot warnings and the unmatched-paths warning go to stderr. Everything else, including the conflict and bookmark warnings, goes to stdout through the pager. The changes are rendered into a buffer first, so the "Working copy changes:" header is printed only if the filtered diff is not empty (source).
6. Algorithm and invariants
Changes against the merged parents
collect_working_copy_status takes @, its parents, @'s tree and
the parents' merged tree (source). For a merge commit the
parent tree is the auto-merge of all parents, so a clean merge with no edits on top shows no changes
(test). The changes are rendered with the ordinary diff renderer in
Summary format, one M/A/D/R/C
line per path (source).
Whether anything changed is decided on the whole tree, before the path filter: tracked
changes exist when the two trees' ids differ (source), and
untracked changes when the snapshot found any untracked path (source).
So jj status file_1 prints "The working copy has no changes." only when nothing changed
anywhere. If only other files changed it prints neither that line nor a header, just the
@/@- lines (test,
test).
Collapsing untracked paths
The untracked paths that match the filter are passed, in sorted order, to
visit_collapsed_untracked_files together with
@'s tree. For each path it walks the parent directories down a stack of tracked subtrees.
At the first directory with no tracked subtree it prints that directory with a trailing separator and
skips every later path under it; if every directory exists in the tracked tree it prints the file
itself (source). The stack is reused between consecutive paths,
so siblings do not reload the same subtrees (source).
Paths are printed relative to the current directory, so from inside an untracked sub/ the
collapsed entry is ./ (test). A file that was
untracked with jj file untrack but still exists shows up as D in the changes
and again under "Untracked paths" (test); see
jj file untrack.
Conflicts and the resolving hint
If @ has a conflict, status warns and prints each conflicted path that
matches the filter with its number of sides and anything unusual among them (a deletion, an executable,
a symlink, a directory or a submodule), after simplifying the conflict
(source, source).
It then finds the ancestors of @'s parents that have conflicts, minus the immutable set (source), and passes them
to report_repo_conflicts. That prints the hint only if the list is not empty and
hints.resolving-conflicts is on; it names the roots of those commits as
jj new <change id> lines, so with a chain of conflicted commits it points at the
first one (source, test).
If @ is the only conflicted commit, as when a merge conflicts in @ itself, no
hint is printed.
If @ is not conflicted but a parent is, it prints "Hint: Conflict in parent commit has been
resolved in working copy." once (source).
jj resolve is the tool the hint suggests.
Conflicted bookmarks
Last, for any workspace state, it reads the view: local bookmarks whose target is not resolved, and
remote bookmarks (name@remote) whose target is not resolved
(source). Each group gets a warning, one indented name per line,
and a hint: jj bookmark set for local ones, fetching again for remote ones
(source). A conflicted bookmark arises from concurrent
operations, as in the test; see
how the view merges refs.
Invariants: status never rewrites a commit or records an operation of its
own; any operation comes from the snapshot. The path filter never hides the @/@-
lines or the bookmark warnings, and never changes whether "no changes" is printed.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
No working copy. | The workspace has no working-copy commit, for example after jj workspace forget. Bookmark conflicts are still reported. | status.rs, test |
Warning: No matching entries for paths: <paths> | An explicit path is absent from @'s tree. Only @'s tree is checked, so a path deleted in @, or an untracked file that is listed under "Untracked paths", still triggers it. | status.rs, cli_util.rs, test |
Warning: Refused to snapshot some files: with sizes, and a hint to raise snapshot.max-new-file-size | A new file is larger than the limit. The hint's example command is jj --config snapshot.max-new-file-size=<n> status. | cli_util.rs, cli_util.rs |
Warning: Skipped some paths because they are not valid UTF-8: | A file name cannot be represented as a repo path. | cli_util.rs |
Warning: There are unresolved conflicts at these paths: | @ is conflicted. Printed even if the filter matches no conflicted path. | status.rs |
Warning: These bookmarks have conflicts: / These remote bookmarks have conflicts: | A local or remote bookmark has a conflicted target. | status.rs |
| A working-copy error | The snapshot fails, or the working copy is stale and snapshot.auto-update-stale is off (the default). | cli_util.rs, working copy |
8. Example output
From upstream's test_status_copies: @ modifies
copy-source, adds copy-target as a copy of it, and renames
rename-source. Copy records turn the add and the delete into C and R:
$ jj status
Working copy changes:
M copy-source
C {copy-source => copy-target}
R {rename-source => rename-target}
Working copy (@) : rlvkpnrz c2fce842 (no description set)
Parent commit (@-): qpvuntsm ebf799bc (no description set)
With snapshot.auto-track = "none()", from test_status_untracked_files,
after two files were tracked explicitly and committed. sub/ now has a tracked file, so its
untracked file is listed by name:
$ jj status
Untracked paths:
? always-untracked-file
? sub/always-untracked
Working copy (@) : mzvwutvl daa133b8 (empty) (no description set)
Parent commit (@-): qpvuntsm b8c1286d (no description set)
A conflict two commits back, from upstream's conflict hints test.
The hint names mzvwutvl, the merge where the conflict started, not the parent:
$ jj status
The working copy has no changes.
Working copy (@) : yqosqzyt 06b8a9dd (conflict) (empty) boom-cont-2
Parent commit (@-): royxmykx fc966143 (conflict) (empty) boom-cont
Warning: There are unresolved conflicts at these paths:
conflicted1.txt 2-sided conflict
conflicted2.txt 2-sided conflict
Hint: To resolve the conflicts, start by creating a commit on top of
the first conflicted commit:
jj new mzvwutvl
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you can inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.