jj / How jj workspace root works

How jj workspace root works

jj workspace root prints a workspace's root directory. For the current workspace it is jj root; with --name it looks another workspace up in the repo's 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. The current workspace
  8. A named workspace
  9. A path that is gone
  10. 7. Errors and edge cases
  11. 8. Example output

In short: without --name, jj prints the root of the workspace it found when it started, without loading the repo. With --name, it loads the repo without snapshotting, checks that the name is one of the view's workspaces, and reads that workspace's recorded path from .jj/repo/workspace_store/index. A missing record is an error; a recorded path that no longer exists is only a warning, and the path is still printed.

The command is cmd_workspace_root. The help text: "Show the workspace root directory" (source). It is the Root(WorkspaceRootArgs) variant of WorkspaceCommand (source, dispatch). To see every workspace's root at once, use jj workspace list.

1. Command-line surface

FlagMeaning
--name NAMEName of the workspace; the current one if absent (source). Shell completion offers workspace names, which it gets by running jj workspace list with its own template (source).

-R chooses which workspace is "current". --at-op matters only with --name: it picks the view whose workspace names are checked. The path itself comes from the workspace store, which is not versioned by operation.

The global flags are on the CLI page.

2. Configuration

None. The output is the path's bytes and a newline, with no template and no colour (source).

3. End-to-end flow

jj workspace root: call flow Without --name, the command takes the workspace loader built at startup and uses its workspace root. With --name, it loads the repo without snapshotting, checks the name against the view's wc_commit_ids, reads the recorded path from the workspace store, and warns if that path does not exist. Either way the path is written to stdout as bytes followed by a newline. no --name: workspace_loader() .workspace_root(); repo never loaded --name: workspace_helper_no_snapshot load repo at the resolved op name in view.wc_commit_ids()? no: "No such workspace" workspace store: get_workspace_path none: "Workspace has no recorded path" path.exists()? no: warning, carry on path_to_bytes → stdout, newline the same for both branches

4. Core architecture touchpoints

PieceRole here
workspace_loader()The loader for the workspace found at startup by searching up from the current directory (or given by -R); its workspace_root() is the answer without --name. See the jj root page.
View::wc_commit_idsThe list of workspaces the repo knows about; --name must be a key in it. See the view page.
SimpleWorkspaceStoreThe map from workspace name to root path, kept in .jj/repo/workspace_store/index. jj workspace add writes an entry, remove deletes it (source) and rename moves it (source). forget deliberately leaves it, so that jj undo can point the workspace back at its directory (source).

5. What it reads, and snapshotting

It never snapshots. Without --name it reads nothing beyond what startup already found: the repo is not loaded. With --name it calls workspace_helper_no_snapshot, which loads the repo at the head operation (or --at-op) to read the view, then reads the workspace store index and checks whether the path exists. It starts no transaction and writes nothing.

6. Algorithm and invariants

The current workspace

command.workspace_loader()?.workspace_root() (source), the same call jj root makes; the two commands print the same thing. From a subdirectory or a secondary workspace, the answer is that workspace's own root (test).

A named workspace

The name must be in the loaded view's wc_commit_ids, else "No such workspace" (source, error). Then the workspace store gives the recorded path, joined to the repo directory and normalized (source). Two checks are kept separate: being in the view decides whether the workspace exists, and the store only says where it lives. That matters because a forgotten workspace keeps its store entry: without the view check, its old path would still be printed.

A path that is gone

If the recorded path does not exist, jj prints "Cannot access workspace path" as a warning and still prints the path (source). This is the strict check that jj workspace list's root keyword leaves out: upstream's comment there points here for it.

7. Errors and edge cases

SituationResult
Name not in the viewError: No such workspace: <name>, including a workspace's old name after a rename (test).
Workspace created before the workspace store existedError: Workspace has no recorded path: <name> (source, test).
Workspace restored by jj undo after jj workspace removeAlso "no recorded path": remove deleted the store entry, which undo does not restore. Forget it and add it again (test).
Recorded directory deletedWarning on stderr, path on stdout, exit 0 (test).
Path not valid as bytes on this platformA user error from path_to_bytes (source).
Not in a workspaceAs for jj root.

8. Example output

From upstream's tests, with workspaces at $TEST_ENV/main and $TEST_ENV/secondary ($TEST_ENV is the test's temporary directory; source, source):

main/subdir$ jj workspace root
$TEST_ENV/main

main$ jj workspace root --name secondary
$TEST_ENV/secondary

main$ rm -r ../secondary
main$ jj workspace root --name secondary
Warning: Cannot access workspace path: $TEST_ENV/secondary
$TEST_ENV/secondary