In short: jj picks a name (the destination's basename unless --name), refuses a name already
in use or a non-empty directory, and creates the directory. In a colocated repo it may first add a Git
worktree there. It writes .jj/repo as a pointer to the existing repo, records the workspace
(operation add workspace '<name>', on the root commit), copies the sparse patterns, and
then creates the real working-copy commit on the chosen parents (operation
create initial working-copy commit in workspace <name>).
The command is cmd_workspace_add. Its counterparts are
jj workspace forget and
jj workspace remove. How a workspace's
working copy is tracked is on the working copy page.
1. Command-line surface
| Flag | Type | Default | Meaning |
|---|---|---|---|
DESTINATION (positional) | path | — | Where to create the workspace (source). |
--name NAME | workspace name | basename of the destination | (source) |
-r, --revision | revsets, repeatable | parents of the current @ | Parents of the new working-copy commit, as if by jj new r1 r2 … (source). |
-m, --message | string, repeatable | empty | Description of the new working-copy commit (source). |
--colocate / --no-colocate | bool | see §2 | Create (or not) a Git worktree for the workspace (source). |
--sparse-patterns | copy, full, empty | copy | Copy the current workspace's patterns, include everything, or include nothing (source). |
2. Configuration
Without --colocate or --no-colocate, a Git worktree is created only when the
current workspace is colocated and git.colocate is true (its default)
(source). -m descriptions get the usual configured
trailers added (source). The new workspace doesn't get settings of
its own: jj uses the current workspace's settings to create it
(source).
3. End-to-end flow
4. Core architecture touchpoints
- Workspace layout: the new
.jjhas arepofile holding a path (relative if possible) to the existing.jj/repodirectory, and its ownworking_copydirectory (source). That file is how jj tells a secondary workspace from the main one. - Workspace store:
.jj/repo/workspace_store/indexmaps names to paths relative to the repo, written under a lock (add). Other commands use it to find a workspace's directory, for examplejj workspace root --nameandjj workspace remove. - View: each workspace has an entry in the view's
wc_commit_ids, which is what makes it exist in the repo; see the view page. - Git: with colocation,
git worktree add --orphanruns first, so the new directory also works with plaingit(create_worktree);.jj/.gitignorekeeps Git from seeing jj's files (maybe_add_gitignore).
5. Transaction and operation
- Two operations:
add workspace '<name>', which checks out a new commit on the root (init_working_copy), thencreate initial working-copy commit in workspace <name>, which replaces it with the real one (source). The first, now-empty commit is abandoned byedit, so it doesn't show up (see the two operations in a test's op log). - Attribution: both operations are recorded as performed in the workspace the command
was run in, not the new one (source,
source). So
jj undothere undoes them, instead of refusing an operation from another workspace (source). - Sparse patterns are written to the new working copy's state without an operation (source).
6. Algorithm and invariants
The new commit's tree is the merge of its parents' trees, so it starts with no changes of its own
(source). A description is set only if -m is given;
trailers are added only then, because a commit without a description is treated as discardable once it's
no longer a working copy, and a trailer-only description would break that
(source).
Invariants: names are unique and non-empty; jj never writes into a non-empty directory; the current workspace is unchanged.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
This command must be able to update the working copy. | Run with --ignore-working-copy or --no-integrate-operation, checked before anything else; the hint names the flag to drop. --at-op is allowed. | add.rs, check |
New workspace name cannot be empty | --name ""; nothing is created. | add.rs, test |
Destination path is not valid UTF-8 | The basename can't be a name. | add.rs |
Workspace named '<name>' already exists | The name is in the view; nothing is created. | add.rs, test |
Destination path exists and is not an empty directory | Including re-adding a forgotten workspace in place, which a source TODO says should work. | add.rs, test |
Warning: Workspace created inside current directory. … | DESTINATION has no path separator, so it may have been meant as a name. | add.rs, test |
8. Worked example
From upstream's test_jj commit -m initial in workspace default, jj workspace add --name second ../secondary
creates a sibling of default@ on initial and checks out its one file:
Created workspace in "../secondary"
Working copy (@) now at: rzvqmyuk bcc858e1 (empty) (no description set)
Parent commit (@-) : qpvuntsm 7b22a8cb initial
Added 1 files, modified 0 files, removed 0 files
Both workspaces then show in the log and in jj workspace list (source):
@ 504e3d8c1bcd default@
│ ○ bcc858e1d93f second@
├─╯
○ 7b22a8cbe888 "initial"
◆ 000000000000
default: . rlvkpnrz 504e3d8c (empty) (no description set)
second: ../secondary rzvqmyuk bcc858e1 (empty) (no description set)