jj / How jj workspace list works

How jj workspace list works

jj workspace list prints one line per workspace the repo knows about: its name, its root directory if one is recorded, and its working-copy commit. The list comes from the view; the root comes from a separate workspace store.

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. Order
  8. The default line
  9. Where root comes from
  10. Marking the current workspace
  11. 7. Errors and edge cases
  12. 8. Example output

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

FlagMeaning
-T, --template TEMPLATERender 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

SettingEffect
templates.workspace_listThe default template, used when -T is absent (source). Its built-in value is builtin_workspace_list (source).
template-aliasesThe 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.
ColoursOutput 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

jj workspace list: call flow workspace_helper loads the repo and snapshots the working copy. The template text comes from -T or templates.workspace_list and is parsed in the commit template language as a WorkspaceRef template. The command iterates the view's wc_commit_ids map in name order, loads each working-copy commit from the store, wraps name and commit in a WorkspaceRef and renders it to stdout. The root keyword reads the workspace store's index file and resolves the recorded path against the repo directory. workspace_helper: load repo at head op, snapshot the snapshot can record an operation; the listing itself does not parse -T or templates.workspace_list as TemplateRenderer<WorkspaceRef> labelled workspace_list, workspace for (name, commit id) in view.wc_commit_ids() a BTreeMap, so sorted by workspace name get_commit_async(id) WorkspaceRef::new(name, commit) root: workspace store lookup only if the template asks for it read workspace_store/index path joined to .jj/repo, normalized template.format(ws_ref) → stdout one line per workspace

4. Core architecture touchpoints

PieceRole here
View::wc_commit_idsThe set of workspaces: every workspace name with its working-copy commit, recorded in each operation's view. See the view page.
WorkspaceRefA 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).
SimpleWorkspaceStoreRecords 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 storeEach 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

SituationResult
A workspace's directory was deletedStill listed with its recorded path until jj workspace forget (test).
Workspace with no recorded rootThe root column is left out of the default line; root alone renders nothing.
Invalid -T templateA template parse error before anything is printed (source).
Not in a workspaceAs 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