jj / How jj bookmark create works

How jj bookmark create works

jj bookmark create adds one or more new local bookmarks pointing at a commit. It refuses names that already exist, including deleted bookmarks still tied to a remote. It can also start tracking the matching remote bookmarks automatically.

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. 7. Errors and edge cases
  8. 8. Worked example

In short: jj resolves the target (default @) and checks every name. A present local bookmark is an error, and so is a name with tracked remote bookmarks. It warns if the target is empty. In one transaction it sets each bookmark to point at the target. For every configured remote whose auto-track pattern matches the name, and which jj knows about, it also tracks name@remote. The operation is create bookmark <names> pointing to commit <id>.

The command is cmd_bookmark_create. It is the strict sibling of jj bookmark set, which creates or moves. To move existing bookmarks by where they point, see jj bookmark move. How bookmarks are stored, including conflicted targets and remote tracking, is on the view page.

1. Command-line surface

FlagTypeDefaultMeaning
NAMES (positional)bookmark names, one or morerequiredThe bookmarks to create. Each is parsed as a revset symbol, so names needing quotes must be quoted (source, parse_bookmark_name).
-r, --revision, --to REVSETrevset@The target, exactly one commit. The --to alias matches set and move (source).

BookmarkCreateArgs

2. Configuration

KeyDefaultEffect
remotes.<name>.auto-track-bookmarksunsetA string pattern. New bookmarks whose names match are tracked on that remote. It also applies to bookmarks fetched later (docs).
remotes.<name>.auto-track-created-bookmarksunsetLike the above, but only for bookmarks created with bookmark create/set. When both are set, a name matching either one is tracked (parse_remote_auto_track_bookmarks_map_for_new_bookmarks).

A remote only counts if the repository already has a view of it, for example after jj git remote add. Patterns for unknown remotes are ignored (source), as upstream's test_create_and_set_auto_track_bookmarks checks.

3. End-to-end flow

jj bookmark create: call flow cmd_bookmark_create loads the workspace through the shared lifecycle, which snapshots the working copy, and resolves the single target. For each name it fails if a local bookmark of that name is present, or if tracked remote bookmarks exist for it. It warns if the target is empty. In a transaction it sets each local bookmark target, then for each remote whose auto-track pattern matches and that the repo knows, it tracks the remote bookmark, warning if it already exists there. It prints a summary and tx.finish records "create bookmark … pointing to commit …". Shared lifecycle: load workspace and repo, snapshot the working copy (CLI page) resolve_single_rev(--revision, default @); for each name: local bookmark present → "Bookmark already exists: …" (hint: use jj bookmark set) tracked remote bookmarks exist → "Tracked remote bookmarks exist for deleted bookmark: …" target discardable → "Warning: Target revision is empty." start_transaction; matchers = auto-track-bookmarks ∪ auto-track-created-bookmarks per remote for each name: set_local_bookmark_target(name, normal(target)) — also makes the target a visible head for each remote whose pattern matches and that has a remote view: already on the remote? "Auto-tracking bookmark that exists on the remote: name@remote" track_remote_bookmark(name@remote) — merges the remote target into the local one "Created N bookmarks pointing to …"; tx.finish(ui, "create bookmark <names> pointing to commit <id>"): write the operation, export to Git if colocated (CLI page)

4. Core architecture touchpoints

StructureRead / writeHowExplained on
View.local_bookmarksread, writeChecked for an existing entry, then each name is set to RefTarget::normal(target).view
View.remote_views (remote bookmarks)read, maybe writeChecked for tracked remote bookmarks of the same name. With auto-tracking, the remote bookmark's state becomes Tracked, and its target is merged into the local one (track_remote_bookmark).view
View.head_idsmaybe writeSetting a bookmark adds its target as a head, so pointing a bookmark at a hidden commit makes it visible again (set_local_bookmark_target).view
Target commitreadResolved, and checked for being empty and undescribed. It is not modified.commits
OperationwriteOne operation, described below.operations

5. Transaction and operation

  • Transaction: one, after all names pass their checks (source). Any failing name aborts the whole command before anything is written.
  • Operation description: create bookmark <name1, name2, …> pointing to commit <full hex id> (source). Upstream's duplicate test shows one in an undo message: create bookmark c pointing to commit 387b9287….
  • Commits created or rewritten: none.
  • Working copy: snapshotted at the start, and otherwise untouched.
  • jj undo removes the bookmarks again, and undoes any auto-tracking.

6. Algorithm and invariants

Each name is checked before anything changes (source). A present local bookmark means the name is taken. A deleted local bookmark whose remote bookmarks are still tracked also counts as taken. jj treats re-pointing it as resurrection, which belongs to bookmark set. Untracked remote bookmarks don't block creation. Creation itself is an unconditional overwrite with a single-target RefTarget. No fast-forward rule applies, since there's no previous target.

Auto-tracking runs per name and per remote (source). The per-remote matchers are the union of auto-track-bookmarks and auto-track-created-bookmarks. Remotes without a view in the repository are skipped. track_remote_bookmark merges the remote bookmark's current target into the new local one (source). The merge base is the remote bookmark's tracked target, which is absent for a bookmark that wasn't tracked. So if name@remote already points elsewhere, the new local bookmark can end up conflicted, which is why jj warns first.

Invariants: never overwrites an existing bookmark. Never rewrites a commit. Either every requested bookmark is created, or none is.

7. Errors and edge cases

MessageWhenSource
Bookmark already exists: <name>, hint Use `jj bookmark set` to update it.A local bookmark of that name is present.create.rs
Tracked remote bookmarks exist for deleted bookmark: <name>, with hints to use bookmark set or bookmark untrackNo local bookmark, but name@remote is tracked somewhere, ignoring the git pseudo-remote.create.rs, cli_util.rs
Warning: Target revision is empty.The target is discardable: no changes and no description.create.rs
Warning: Auto-tracking bookmark that exists on the remote: <name>@<remote>An auto-tracked remote already has a bookmark with that name.create.rs
Failed to parse bookmark name: … (clap: invalid value)The name isn't a valid revset symbol, for example empty.revset_util.rs
Revset `…` resolved to more than one revision (or none)-r isn't exactly one commit.revset_util.rs

8. Worked example

From upstream's test_bookmark_multiple_names and test_bookmark_move. In a fresh repository, jj bookmark create -r@ foo bar creates two bookmarks on the empty working-copy commit. Later, after foo was pushed and then deleted locally, jj bookmark create foo is refused:

jj bookmark create: two outcomes Left: before, @ (qpvuntsm e8849ae1) has no bookmarks; after jj bookmark create -r@ foo bar it has bookmarks bar and foo, with a warning that the target is empty. Right: bookmark foo was deleted locally but foo@origin is still tracked, so jj bookmark create foo fails and nothing changes. create -r@ foo bar create foo (foo@origin tracked) root @ e8849ae1 no bookmarks root @ e8849ae1 bar foo foo (deleted) @origin: qpvuntsm 5f3ceb1e (empty) commit Error: Tracked remote bookmarks exist for deleted bookmark: foo → use jj bookmark set, or jj bookmark untrack foo

Upstream's snapshots of the two runs:

Warning: Target revision is empty.
Created 2 bookmarks pointing to qpvuntsm e8849ae1 bar foo | (empty) (no description set)
Error: Tracked remote bookmarks exist for deleted bookmark: foo
Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack foo` to disassociate them.

After jj bookmark untrack foo and jj bookmark delete foo, the same create succeeds, and foo@origin stays as an untracked remote bookmark.