jj / How jj git init works

How jj git init works

jj git init creates a jj repo backed by Git in a directory. The Git repository can be new and colocated (a .git next to .jj, the default), new and hidden inside .jj, or an existing one, whose branches, tags and HEAD are imported.

On this page
  1. 1. Command-line surface
  2. 2. Configuration
  3. 3. End-to-end flow
  4. 4. Core architecture touchpoints
  5. 5. Transaction and operation
  6. 6. Algorithm and invariants
  7. Choosing the mode
  8. Setting trunk()
  9. Untracked remote bookmarks
  10. 7. Errors and edge cases
  11. 8. Worked example

In short: jj rejects --at-op, --ignore-working-copy and --no-integrate-operation, creates the directory if needed, and decides the mode. With --git-repo, or when colocating in a directory that already has .git, it uses that existing repository: it imports its refs, sets trunk() from upstream/HEAD or origin/HEAD, and checks out a new commit on top of Git's HEAD. Otherwise it creates a fresh Git repo, colocated or internal, and the working-copy commit sits on the root commit. It ends with Initialized repo in "…".

The command is cmd_git_init, and the work is in do_init. It shares the workspace creation with jj git clone and the ref import with jj git import. What a workspace and repo consist of is on the working copy page.

1. Command-line surface

FlagTypeDefaultMeaning
DESTINATION (positional)path.Where to create the workspace; created if missing, but its parent must exist (source).
--colocateboolfalseColocate with Git. Has no effect unless git.colocate is false; conflicts with --git-repo (source).
--no-colocateboolfalseKeep the Git repo inside .jj; conflicts with --colocate (source).
--object-hashsha1 or sha256git.object-hashHash algorithm for a newly created Git repo; conflicts with --git-repo (source).
--git-repo PATHpathnoneUse this existing Git repository; conflicts with --colocate and --object-hash (source).

GitInitArgs

2. Configuration

KeyDefaultEffect
git.colocatetrueWhether to colocate; the flags override it (default, use).
git.object-hash"sha1"Hash algorithm when --object-hash isn't given (default, use).
remotes.<name>.auto-track-bookmarks"~*" per the schemaWhen importing an existing repo, which remote branches get tracked local bookmarks (use).

Settings are read for the new workspace's path, so conditional config scoped to it applies (source). When importing an existing repo, jj may write revset-aliases."trunk()" to the new repo config (§6).

3. End-to-end flow

jj git init: call flow cmd_git_init refuses --no-integrate-operation, --ignore-working-copy and --at-op, creates or reuses the directory and resolves colocation and the object hash. do_init picks a mode. Colocate and Internal create a new Git repo with init_colocated_git or init_internal_git. External uses init_external_git, then imports refs, snapshots, sets trunk from upstream or origin HEAD, and checks out Git's HEAD, then hints about untracked remote bookmarks and warns inside a Git worktree. Finally it prints Initialized repo. refuse --no-integrate-operation, --ignore-working-copy, --at-op create_or_reuse_dir(destination); resolve colocate and object hash do_init: choose the mode (see §6) and load settings for the new path Colocate init_colocated_git .jj/.gitignore Internal init_internal_git .jj/repo/store/git External (existing Git repo) init_external_git: store points at the existing repo init_git_refs: import_refs (+ export if colocated) → op "import git refs" .gitignore, maybe_snapshot, set trunk() from upstream/origin HEAD check out Git HEAD → op "import git head" non-colocated here; colocated: done by the snapshot's HEAD import hint untracked remote bookmarks; warn inside a Git worktree "Initialized repo in "DIR""
  1. Pre-checks. The three global flags don't make sense before a repo exists and are refused. The destination is created with create_or_reuse_dir, which creates one directory level only and is fine if it already exists, then canonicalized (source).
  2. New repo. For Colocate and Internal, the workspace is created exactly as in jj git clone, with one operation, add workspace 'default', whose working-copy commit is an empty child of the root (source). A colocated repo also gets .jj/.gitignore (maybe_add_gitignore).
  3. Existing repo. Workspace::init_external_git records the Git repo's path, relative if it is inside the workspace so the workspace can be moved. Refs are imported first "so all the reachable commits are indexed in chronological order" (source).
  4. Import. init_git_refs runs import_refs with abandoning and synthetic predecessors turned off. If anything changed, a colocated repo also exports right away, because auto-tracking may have created local bookmarks. The transaction is committed directly as import git refs, and jj prints Done importing changes from the underlying Git repo. (source).
  5. HEAD. In a colocated repo, maybe_snapshot imports Git's HEAD and puts the working-copy commit on top of it (import_git_head). Otherwise jj imports the HEAD commit itself and checks out a new commit on top, recorded as import git head (source, import_head_commit).

4. Core architecture touchpoints

  • Workspace and repo creation: .jj/repo with its stores, and .jj/working_copy; on failure, .jj is removed (init_with_factories).
  • Git backend: new (bare internal or colocated) or external; see the commits page.
  • View: imported bookmarks, tags and remote refs, the working-copy commit, and in a colocated repo the recorded Git HEAD; see the view page.
  • Working copy: checking out Git's HEAD updates the files in a non-colocated workspace (Added N files); in a colocated one the files are already there and only the working-copy state is reset.

5. Transaction and operation

OperationWhenSource
add workspace 'default'Always.workspace.rs
import git refsExisting repo with any refs to import.init.rs
import git headExisting repo whose HEAD points at a commit: by the snapshot when colocated, by do_init otherwise.cli_util.rs, init.rs

init_git_refs uses a bare start_repo_transaction and commits it itself, because no workspace command helper exists yet at that point (source).

6. Algorithm and invariants

Choosing the mode

How jj git init chooses its mode With --git-repo, the mode is External at that path, with .git appended unless the path ends in .git or the .git child doesn't exist, which suggests a bare repo. Otherwise, when colocating, an existing .git in the destination makes it External at that .git, and no .git makes it Colocate. When not colocating, an existing .git is an error, and no .git makes it Internal. --git-repo PATH append .git unless it ends in .git or PATH/.git is missing (bare) colocate does DEST/.git exist? not colocate does DEST/.git exist? External(PATH) yes: External (DEST/.git) no: Colocate yes: error no: Internal External is colocated if the Git repo's working tree is the workspace (is_colocated_git_workspace)

So with the default git.colocate = true, running jj git init in an existing Git checkout adopts it as a colocated repo (test). Whether an external repo counts as colocated is decided by opening the Git repo at the workspace root (is_colocated_git_workspace).

Setting trunk()

For an existing repo, jj looks for refs/remotes/upstream/HEAD, then refs/remotes/origin/HEAD. The first one that exists decides: if it is a symbolic ref to a branch, trunk() is set to that branch at that remote in the repo config, and jj prints Setting the revset alias `trunk()` to `…`. If it exists but can't be parsed, jj stops without setting anything rather than falling back to origin (maybe_set_repository_level_trunk_alias).

Untracked remote bookmarks

After an import, any local bookmark that has an untracked remote bookmark of the same name is listed, with a ready-made jj bookmark track … command (print_trackable_remote_bookmarks). See jj bookmark track.

Invariants: init never creates a second Git repository next to an existing .git. Importing an existing repo never abandons commits. A non-colocated workspace doesn't record a Git HEAD (test).

7. Errors and edge cases

MessageWhenSource
--no-integrate-operation is not respected, --ignore-working-copy is not respected, --at-op is not respected (exit 2)One of those global flags was given.init.rs
Failed to create workspaceThe directory can't be created: the path is an existing file, or its parent doesn't exist.init.rs, test
Did not create a jj repo because there is an existing Git repo in this directory., hint run `jj git init --colocate` insteadNot colocating, and the directory has .git.init.rs, test
Warning: Initialized a new colocated jj repo inside a Git worktree., hint to use jj workspace addColocated with a linked Git worktree.init.rs
Hint: The following remote bookmarks aren't associated with the existing local bookmarks:An imported local bookmark has an untracked remote counterpart.init.rs

8. Worked example

From upstream's test_git_init_external_import_trunk (non-bare case). An existing Git repo has my-bookmark at commit e80a42cc, a remote branch origin/trunk at the same commit, and origin/HEAD pointing at it. jj git init repo --git-repo <path> imports the refs, sets trunk(), and checks out a new commit on top of Git's HEAD:

The new repo after jj git init --git-repo The root commit, then e80a42cc "My commit message" carrying my-bookmark and trunk@origin, which trunk() now resolves to, and a new empty working-copy commit ed6b5138 on top. root e80a42cc My commit message my-bookmark, trunk@origin = trunk(); Git HEAD ed6b5138 @ (empty) new working-copy commit repo config written by init revset-aliases."trunk()" = "trunk@origin"

Upstream's output (source):

Done importing changes from the underlying Git repo.
Setting the revset alias `trunk()` to `trunk@origin`.
Working copy  (@) now at: sqpuoqvx ed6b5138 (empty) (no description set)
Parent commit (@-)      : nntyzxmz e80a42cc my-bookmark trunk@origin | My commit message
Added 1 files, modified 0 files, removed 0 files
Initialized repo in "repo"

When the same kind of repo is adopted in place with jj git init --colocate, there is no Working copy now at line: the snapshot's HEAD import puts the working-copy commit on top of HEAD without touching the files, and the workspace records that Git HEAD (source).