In short: for the main workspace, enable renames .jj/repo/store/git to
.git, rewrites the git_target file to ../../../.git, sets
core.bare = false, writes .jj/.gitignore, and points Git's HEAD at the
working-copy commit's parent. disable reverses each step and clears the recorded Git
HEAD. A secondary workspace is colocated with a Git worktree instead of a move. Only the
HEAD change is recorded as an operation.
The group is GitColocationCommand. New repos choose
colocation at creation, in jj git init or
jj git clone. What changes day to day in a
colocated workspace (automatic import and export) is described on the
jj git import and
jj git export pages.
1. Command-line surface
| Subcommand | Arguments | Meaning |
|---|---|---|
status | none | Show the current colocation status (source). |
enable | none | Convert into a colocated workspace (source). |
disable | none | Convert into a non-colocated workspace; plain git then no longer works there (source). |
2. Configuration
git.colocate doesn't affect these commands; it only sets the default for new repos. The
state they change lives on disk: the .jj/repo/store/git_target file that tells the Git backend
where its repository is, core.bare in the Git repository's own config, and
.jj/.gitignore. enable and disable for a secondary workspace run
git as a subprocess, so the git.executable-path setting applies
(source).
3. End-to-end flow
The secondary-workspace box and the main-workspace box are alternatives: a workspace whose
.jj/repo is a file pointing at another workspace's repo is a secondary ("child") workspace
(is_child_workspace).
status
Prints whether the workspace is colocated, by trying to open a Git repo at the workspace root that
matches the backend's (is_colocated_git_workspace), and the
Git HEAD recorded in the view, or (none). A hint then says how to switch. For a main
workspace with no .jj/repo/store/git, the backing repo is external and the hint says colocation
can't be enabled (source).
4. Core architecture touchpoints
- Git backend location: the backend finds its Git repository through
git_target, relative to the store directory;"git"and"../../../.git"are the two values these commands write (enable, disable). See the commits page for the Git backend. - Git worktrees:
git::create_worktreerunsgit worktree add --orphanwith a randomjj-worktree-…branch name and leavesHEADunborn. If the directory already has files, which it does here, the worktree is made in a temporary directory, its.gitlink moved into place andgit worktree repairrun (source). - View: the per-workspace Git
HEAD(git_head) is the only view change; see the view page. - Working copy: files aren't touched.
reset_headalso rewrites Git's index from the working-copy commit's (merged) parent tree, sogit diffshows the same changes asjj diff(reset_index). It holds Git'sindex.lockfrom before it reads the old index until the new one is committed, waiting up to a second if another process has it, so a concurrentgit addisn't silently overwritten (lock_index).
5. Transaction and operation
enable: one transaction after the move,set git head to working copy parent, recorded only if the GitHEADchanged (source, test).disable: one transaction,remove git head reference, recorded only if a GitHEADwas recorded (source, test).status: none.
The move itself happens outside any transaction, before it, so jj undo only restores the
recorded HEAD; it doesn't move the Git repository back. Both commands reload the workspace at
the operation they started from, so the transaction is based on the same repo state
(reload_workspace_helper).
6. Algorithm and invariants
Releasing the repo before moving it. The workspace helper holds the Git repository
open, and gix memory-maps pack files; Windows refuses to rename a directory with open files.
So the helper is dropped before the rename and a fresh one loaded afterwards
(source, test with pack files).
Bare flag. An internal repo is bare and a colocated one isn't, so
set_git_repo_bare sets core.bare directly in the
moved repository's config file.
Git HEAD. On enable, reset_head sets Git's HEAD to the
working-copy commit's first parent, detached, or leaves it unborn if that parent is the root commit. It
is called with no previous working-copy commit, which forces a full reset: it also clears an in-progress Git
merge, rebase, cherry-pick or bisect state, and rewrites the index from the parent tree
(call, source). On disable, jj just forgets the recorded
HEAD, since a non-colocated workspace doesn't track one.
Invariants: after enable, the workspace is colocated, .jj is
ignored by Git, and Git's HEAD matches the working-copy parent. After disable, no
.git is left at the root and no Git HEAD is recorded. Running either twice is
harmless: the second run says the workspace is already in that state.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
Workspace is already colocated with Git. / Workspace is already not colocated with Git. | Nothing to do. | enable, disable |
This command requires a working copy | enable in a workspace with no working-copy commit. | colocation.rs |
A .git directory already exists in the workspace root. Cannot colocate. | Something is already at .git. | colocation.rs, test |
Cannot colocate a workspace backed by an external Git repository at … | The repo was created with --git-repo, so there is no internal store to move. | colocation.rs, test |
Failed to move Git repository … | Any other rename failure. | enable, disable |
Failed to open Git config file., Failed to set core.bare …, Failed to write to Git config file … | Changing core.bare failed. | colocation.rs |
Removed Git worktree for "…". or a warning that it failed | disable in a secondary workspace; a failure is only a warning. | git_util.rs, test |
8. Worked example
From upstream's test_git_colocation_enable_success. A repo made with
jj git init --no-colocate repo, followed by jj new, so the working-copy commit has
a non-root parent e8849ae1. Then jj git colocation enable:
Upstream's output, then jj git colocation status and jj op show (source):
Workspace successfully converted into a colocated Jujutsu/Git workspace.
Workspace 'default' is currently colocated with Git.
Last imported/exported Git HEAD: e8849ae12c709f2321908879bc724fdb2ab8a781
set git head to working copy parent
test_git_colocation_disable_success is the reverse: .git moves
back into the store, git_target is git again, .jj/.gitignore is gone, the
recorded HEAD is (none), and the operation is remove git head reference.