In short: jj loads the workspace and repo and snapshots the working copy, like most commands. Loading the
repo loads the commit backend, and the Git backend reads .jj/repo/store/git_target, joins
it to the store directory and opens the Git repo there. jj git root asks the store for that
backend, fails if the repo uses another backend, and prints the backend's Git directory with a newline:
.jj/repo/store/git for a plain jj git init, the .git directory
for a colocated or external repo.
The command is cmd_git_root. The help text: "Show
the underlying Git directory of a repository using the Git backend"
(source). It is the Root(GitRootArgs) variant of
GitCommand (source), dispatched by
cmd_git; the whole git group only
exists when jj is built with the git feature (source).
Compare jj root, which prints the workspace root instead.
1. Command-line surface
GitRootArgs {} is an empty struct: no arguments or flags of its own
(source). The global flags that matter:
| Flag | Effect on jj git root |
|---|---|
-R, --repository PATH | Use the workspace at PATH, as for every command (see jj root). |
--ignore-working-copy | Skips the snapshot and the colocated Git import. The printed path is the same. |
--at-op | Loads the repo at that operation, which also skips the snapshot (source). The Git path does not depend on the operation, so the output is the same. |
The global flags are on the CLI page.
2. Configuration
No setting changes the output. The settings the Git backend is loaded with, a
GitBackendSettings the caller builds from config
(source: the Git executable, whether to write
change-id headers, and the user name and email it passes to gix), do not affect the path, and cmd_git_root writes straight to stdout without the pager
(source). As with every snapshotting command,
snapshot.auto-update-stale decides what happens if the working copy is stale
(source).
3. End-to-end flow
4. Core architecture touchpoints
| Piece | Role here |
|---|---|
RepoLoader::init_from_file_system | Builds the Store around the backend that load_backend picks from store/type. The Git factory is registered only with the git feature (source). |
GitBackend | Holds base_repo, the gix repository opened at load time. git_repo_path() is base_repo.path(): "the .git directory or the repository itself if it's bare". |
get_git_backend | store.backend_impl(), a downcast of the boxed backend; None becomes UnexpectedGitBackendError, which the CLI turns into a user error (source). |
file_util::path_to_bytes | Turns the path into the bytes written to stdout. |
| Working copy, Git import | Snapshotted before the command runs; see snapshotting and jj git import. |
5. What it reads, and snapshotting
Unlike jj root, jj git root calls
workspace_helper, so it loads the repo at the head
operation and, unless --ignore-working-copy or --at-op is given, takes the Git
import/export lock and snapshots the working copy (source). In a
colocated workspace the snapshot also imports Git HEAD first and Git refs afterwards
(source). Either can record an operation, even though the
command only prints a path. Beyond that it reads store/type and store/git_target,
and gix opens the Git repo; no commit, tree or ref is read for the output itself.
6. Algorithm and invariants
Where git_target points
Every Git-backed repo writes store/git_target once, at init
(source). What it holds depends on how the repo was made:
| Created by | git_target | Printed |
|---|---|---|
jj git init (not colocated) | git, a bare repo inside the store (source) | …/.jj/repo/store/git |
jj git init --colocate | The workspace root relative to the store, plus .git: ../../../.git (source, source) | …/.git |
jj git init --git-repo PATH | Relative to the store if the Git repo is inside the workspace, else the absolute path (source) | That repo's .git |
jj git colocation moves the repo and rewrites this file, so the answer follows it.
A canonical path, mostly
On load the target is joined to the store path and passed through
canonicalize_git_repo_path: symlinks are resolved,
except that a final .git component is kept and only its parent is canonicalized, so a
colocated repo is not reopened as a bare one. gix is told to open that path as is
(source). The printed path is therefore absolute even when
git_target is relative.
Secondary workspaces
A workspace made by jj workspace add shares the
repo, and so the store, of the workspace it came from. jj git root in either prints the same
Git directory.
What it is for
Tools that look for .git cannot find the store of a non-colocated repo. Upstream's GitHub
guide sets GIT_DIR=$(jj git root) so the GitHub CLI works there
(source).
7. Errors and edge cases
| Situation | Message |
|---|---|
| The repo uses another backend, such as the simple backend | "The repo is not backed by a Git repo", exit status 1, after loading and snapshotting (source, test). |
git_target is missing or its target cannot be opened | Loading fails before the command runs: "The repository appears broken or inaccessible" (source, source). |
jj built without the git feature | No git command, and a Git-backed repo has no backend factory: "This version of the jj binary doesn't support this type of repo" (source). |
| Not in a workspace | As for jj root. |
| The path is not UTF-8 (Windows only) | A user error from path_to_bytes; on Unix it cannot fail (source). |
8. Example output
Upstream's tests cover each layout (source); the paths below are illustrative:
$ jj git init repo && cd repo
$ jj git root
/home/alice/repo/.jj/repo/store/git
$ jj git init --colocate other && cd other
$ jj git root
/home/alice/other/.git