Colocated mode: two stores, one directory
Part 2 of 9.
The install itself is a non-event - a package manager or a cargo install, then a
user.name/user.email config, same shape as Git’s own first-run setup. The interesting
part starts at jj git init --colocate.
Run that inside an existing Git checkout and it drops a .jj directory next to .git,
adds .jj/ to .git/info/exclude so it never shows up as untracked, and imports the
current branches and HEAD as jj commits. Nothing in .git is rewritten. If I deleted
.jj right now, the repository would be exactly the plain Git repo it was before,
nothing lost.
That reversibility is what made trying this feel low-risk. It’s not a migration with a
point of no return; it’s closer to installing a second reader on top of storage that
already exists. git log still works. git status still works. Any pre-commit hook,
any CI runner, any teammate’s IDE keeps functioning without modification.
What immediately looked odd was the first jj status:
The working copy has no changes.
Working copy (@) : qpvuntsm 230dd059 (empty) (no description set)
Parent commit (@-): mzvwutvl a1b2c3d4 main | Initial commit
There’s already a commit checked out, and it’s empty. Coming from Git, an “empty commit with no message” sitting at the tip looks like an error state. It isn’t - it’s the working copy itself, always represented as a real commit. That single idea is enough to reorganize how the rest of the tool reads, and it’s the whole subject of the next post.