In short: jj loads the repo at the operation the working copy last recorded, snapshots the files there
(so edits aren't lost), and reloads at the head operation. If the working copy really is stale, it resets
Git's HEAD in a colocated workspace, checks out the desired working-copy commit, and prints
Updated working copy to fresh commit …. If not, it prints Attempted recovery, but the
working copy is not stale. If the working copy's operation can't be read, it creates a
RECOVERY COMMIT on the current working-copy commit and checks that out.
The command is cmd_workspace_update_stale, a thin wrapper
around recover_stale_working_copy. Workspaces become stale
because several share one repo; see jj workspace add
and the working copy page.
1. Command-line surface
No arguments (WorkspaceUpdateStaleArgs). The command must
be able to update the working copy, so --ignore-working-copy is refused
(source, test).
2. Configuration
| Key | Default | Effect |
|---|---|---|
snapshot.auto-update-stale | false | When true, any command that finds the working copy stale runs this same recovery itself instead of failing (default, use). |
3. End-to-end flow
4. Core architecture touchpoints
- Working copy: each working copy records the operation it was last updated to.
Comparing that with the repo's head operation is how staleness is detected
(
WorkingCopyFreshness). The check-out itself verifies the files still match the stale commit, so a concurrent change fails withConcurrent working copy operation. Try again.(update_stale_working_copy). - Operations: the snapshot is written on the old operation, so it may diverge from the head; loading the repo again merges the two. A source comment notes that the merged repo's working-copy commit stays the one it already had (source). See operations.
- Git: in a colocated workspace, Git's
HEADis moved to the new working-copy parent first, as its ownreset git headoperation (source).
5. Transaction and operation
- Snapshot:
snapshot working copy, on the stale operation, only if the files had changed. reset git head, colocated workspaces only, ifHEADmoved.- The check-out records no operation: the repo already says where the working copy should be; only the working copy catches up (source). When nothing is stale, nothing at all is recorded (test).
- Recovery:
recovery commit, with the workspace's name in its metadata (source).
6. Algorithm and invariants
Freshness
An ordinary command treats Stale as an error (The working copy is stale …, with a hint to run
this command) and SiblingOperation as an internal error suggesting jj op integrate
(source). This command handles both by checking out the desired
commit. A source TODO notes that only one common ancestor is tested (source).
Recovery commit
If the working copy's operation is gone (for example, abandoned and garbage-collected), jj can't know
which commit the files were based on. It makes a new commit on top of the workspace's current
working-copy commit with a description starting RECOVERY COMMIT FROM `jj workspace update-stale`,
explaining that its diff may include changes from several commits. It makes that commit the working
copy's recorded state (recover), then snapshots, so the files on disk end up in it
(source, create_and_check_out_recovery_commit).
Invariants: edits in the stale files are snapshotted before anything is checked out, so they aren't lost; untracked files are reported, not deleted.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
Attempted recovery, but the working copy is not stale. | Nothing to do. | cli_util.rs, test |
This command must be able to update the working copy., hint Don't use --ignore-working-copy. | That flag was given. | cli_util.rs |
Concurrent working copy operation. Try again. | The files changed again during recovery. | cli_util.rs |
Failed to read working copy's current operation; attempting recovery. … then Created and checked out recovery commit … | The recorded operation is missing. | cli_util.rs, test |
8. Worked example
From upstream's test_default and secondary both sit on commit 9a462e35. In default,
a change to file is squashed into that parent, which rewrites it to b853f7c8 and
rebases secondary@ onto it. The secondary directory's files now lag behind:
Upstream's output in the secondary directory (source):
$ jj st
Error: The working copy is stale (not updated since operation 8e2504e2592d).
Hint: Run `jj workspace update-stale` to update it.
See https://docs.jj-vcs.dev/latest/working-copy/#stale-working-copy for more information.
$ jj workspace update-stale
Working copy (@) now at: pmmvwywv 90f3d42e (empty) (no description set)
Parent commit (@-) : qpvuntsm b853f7c8 (no description set)
Added 0 files, modified 1 files, removed 0 files
Updated working copy to fresh commit 90f3d42e0bff
The secondary files were clean, so the snapshot recorded nothing and no divergent commit was created (source).