In short: jj loads the repo and snapshots the working copy, then compiles a template over the
WorkspaceRef type, either -T or templates.workspace_list. It walks
the view's wc_commit_ids map, which is sorted by workspace name, loads each working-copy
commit and renders the template once per workspace. The root keyword looks the name up in
.jj/repo/workspace_store/index; it never touches the other workspace's directory, so a
deleted workspace is still listed with its old path.
The command is cmd_workspace_list. The help text:
"List workspaces" (source). It is the
List(WorkspaceListArgs) variant of WorkspaceCommand
(source, dispatch).
Workspaces are made by jj workspace add and
dropped by jj workspace forget.
1. Command-line surface
| Flag | Meaning |
|---|---|
-T, --template TEMPLATE | Render each workspace with this template instead of templates.workspace_list. Keywords are the zero-argument methods of WorkspaceRef (source). Shell completion offers template aliases. |
There is no positional argument and no filter: every workspace is listed. The relevant global flags are
--ignore-working-copy and --at-op; with --at-op the list is the
workspaces as that operation's view recorded them.
The global flags are on the CLI page.
2. Configuration
| Setting | Effect |
|---|---|
templates.workspace_list | The default template, used when -T is absent (source). Its built-in value is builtin_workspace_list (source). |
template-aliases | The built-in template is itself an alias, built from format_commit_summary_with_refs (source), so overriding that alias changes this output too, as it does for other commit summaries. |
| Colours | Output is labelled workspace_list and workspace (source); the commit part is labelled working_copy for the current workspace's commit, and the current workspace's whole line is labelled current_workspace, which colors.toml makes bold (source). |
3. End-to-end flow
4. Core architecture touchpoints
| Piece | Role here |
|---|---|
View::wc_commit_ids | The set of workspaces: every workspace name with its working-copy commit, recorded in each operation's view. See the view page. |
WorkspaceRef | A workspace name plus its working-copy commit. Its methods are name, current_workspace, target and root (source); on its own it formats as name@ (source). |
SimpleWorkspaceStore | Records each workspace's root in .jj/repo/workspace_store/index, written by add relative to the repo directory when possible (source). |
FsPath.relative() | Turns the absolute root into a path relative to the current directory, as the default template does. |
| Commit store | Each working-copy commit is loaded to show its ids, description and bookmarks. See commits. |
5. What it reads, and snapshotting
It calls workspace_helper, so the current workspace
is snapshotted first (and, if colocated, Git HEAD and refs are imported), unless
--ignore-working-copy or --at-op is given. Other workspaces are not snapshotted:
their line shows the commit the view last recorded for them, which may lag their files. Beyond the view it
reads one commit per workspace and, when the template uses root, the workspace store index.
It starts no transaction.
6. Algorithm and invariants
Order
The loop is over wc_commit_ids(), a BTreeMap keyed by workspace name
(source), so workspaces come out sorted by name, not by creation
time, and the current workspace is not moved to the top.
The default line
builtin_workspace_list is name, " (current)" on the current
workspace's line, ": ", then, separated by spaces, the root
relative to the current directory if one is recorded and the commit summary with the commit's bookmarks,
then a newline, all wrapped in the current_workspace label on the current workspace's line
(source). name is a revset symbol, so a
name that is not a plain identifier is quoted (source).
Where root comes from
root is Option<FsPath>. It reads the workspace store's index, finds the
entry with this name and joins its path to the repo directory, resolving .. literally
(source). It is empty for workspaces created before jj 0.38.0,
which recorded no path, and it is not checked against the disk: upstream's comment says list templates
"should also keep rendering if a recorded path is stale or unavailable" and points to
jj workspace root --name for strict checks (source).
Marking the current workspace
The default output marks it with (current) after its name. In a custom template,
current_workspace compares the line's workspace name with the workspace the command runs in
(source). It differs from
target.current_working_copy(), which compares commits
(source): when two workspaces have the same working-copy commit,
both lines have current_working_copy() true, but only one has current_workspace
true (test).
7. Errors and edge cases
| Situation | Result |
|---|---|
| A workspace's directory was deleted | Still listed with its recorded path until jj workspace forget (test). |
| Workspace with no recorded root | The root column is left out of the default line; root alone renders nothing. |
Invalid -T template | A template parse error before anything is printed (source). |
| Not in a workspace | As for jj root. |
8. Example output
From upstream's tests, run in the main workspace after adding second at
../secondary (source):
$ jj workspace list
default (current): . rlvkpnrz 504e3d8c (empty) (no description set)
second: ../secondary pmmvwywv 058f604d (empty) (no description set)
With a custom template that prints both checks, after second has been moved onto
default's commit with jj edit default@
(source):
$ jj workspace list -T 'separate(" ", name ++ ":", target.commit_id().short(),
if(current_workspace, "(current workspace)"),
if(target.current_working_copy(), "(current @)")) ++ "\n"'
default: 504e3d8c1bcd (current workspace) (current @)
second: 504e3d8c1bcd (current @)
And after ../secondary has been deleted from disk, root still prints the
recorded path ($TEST_ENV is the test's temporary directory;
source):
$ jj workspace list -T 'name ++ ": " ++ root ++ "\n"'
default: $TEST_ENV/main
second: $TEST_ENV/secondary