jj / How jj bookmark move works

How jj bookmark move works

jj bookmark move moves existing bookmarks to a commit. You pick them by name pattern, by the commits they currently point at, or both. It never creates a bookmark.

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 (--to, default @). It collects the local bookmarks whose names match the patterns (all of them if none are given), keeping those that point into the --from revsets when that is given. It drops bookmarks already at the target. If nothing is left it prints No bookmarks to update. and stops. Otherwise every match must pass the fast-forward check unless --allow-backwards is set. Then, in one transaction, jj points them all at the target. The operation is point bookmark <names> to commit <id>.

The command is cmd_bookmark_move. Its help text says: "Unlike `jj bookmark set`, this command cannot create new bookmarks" (source). For setting bookmarks by exact name, creating them if needed, see jj bookmark set. Bookmark storage is on the view page.

1. Command-line surface

FlagTypeDefaultMeaning
NAMES (positional)name patternsnone (all bookmarks)Bookmarks to move. Patterns are globs by default, and other string-pattern kinds work too. Several patterns are unioned (source, parse_union_name_patterns).
-f, --from REVSETSrevsets, repeatablenoneOnly move bookmarks that currently point into these revisions. Several are unioned (source).
-t, --to REVSETrevset@The target, exactly one commit (source). Unlike create and set, there's no -r here.
-B, --allow-backwardsbooloffAllow backwards or sideways moves (source).

NAMES and --from form a required clap group: at least one must be given (source). So jj bookmark move alone is a usage error, even though "move every bookmark" would be a possible meaning, as the test notes.

BookmarkMoveArgs

2. Configuration

The command reads no settings of its own. It never creates bookmarks, so the auto-track settings used by create and set don't apply. Revset aliases from [revset-aliases] work in --from and --to, as in any revset.

3. End-to-end flow

jj bookmark move: call flow cmd_bookmark_move loads the workspace, snapshotting the working copy, and resolves the --to target. It evaluates the --from revsets if given, parses the name patterns (all names if none), and keeps local bookmarks matching both. It warns about exact names with no local bookmark and drops matches already at the target. If none remain it prints "No bookmarks to update." and returns. Otherwise, unless --allow-backwards, the first non-fast-forward match fails the command. It warns if the target is empty, sets every match to the target in a transaction, prints "Moved N bookmarks to …" with a hint when several matched without names, and records "point bookmark … to commit …". Shared lifecycle: load workspace and repo, snapshot the working copy (CLI page) resolve_single_rev(--to, default @); select the bookmarks to move --from given: evaluate the union of revsets; a bookmark matches if any present add is in it names: union of patterns (glob by default), or every name; walk local_bookmarks_matching exact names with no local bookmark → "Warning: No matching bookmarks for names: …" drop bookmarks already pointing at the target (not counted) nothing left → "No bookmarks to update." and return unless -B: first match failing is_fast_forward → error empty target → "Warning: Target revision is empty." start_transaction; set_local_bookmark_target(name, normal(target)) for each match "Moved N bookmarks to …" more than one match and no names given → "Hint: Specify bookmark by name to update just one…" tx.finish(ui, "point bookmark <names> to commit <id>"): write the operation, export to Git if colocated

4. Core architecture touchpoints

StructureRead / writeHowExplained on
View.local_bookmarksread, writeIterated by name matcher, then each match is set to RefTarget::normal(target). A conflicted bookmark becomes a normal one.view
Revset engineread--from is evaluated once into a membership test (containing_fn), then checked against each bookmark's present adds (source).revsets
Index (ancestry)readis_ancestor for the fast-forward check (is_fast_forward).index
View.head_idsmaybe writeThe target is added as a head (set_local_bookmark_target).view
OperationwriteOne operation if anything moved. None if nothing matched.operations

5. Transaction and operation

  • Transaction: one, after selection and the fast-forward check (source). When nothing is left to move, the command returns before starting one, so no operation is recorded.
  • Operation description: point bookmark <name1, name2, …> to commit <full hex id>, naming only the bookmarks that actually move (source). It is the same text bookmark set uses.
  • Commits created or rewritten: none.
  • Working copy: snapshotted at the start, and otherwise untouched.

6. Algorithm and invariants

Selection (source) is an intersection. The name side is the union of the patterns, or every name when there are none. The --from side keeps a bookmark if any of its present targets lies in the union of the --from revsets, or keeps everything when --from isn't given. Only local bookmarks are considered. A deleted bookmark, with only remote bookmarks left, can't be moved, since it has no local entry.

The unmatched-name warning (warn_unmatched_local_bookmarks) covers only exact names in the pattern expression that have no local bookmark. A glob like 'a?' that matches nothing gives no warning. A name that exists but is filtered out by --from gives no warning either.

The fast-forward check is the same is_fast_forward as bookmark set: some present side of the old target must be an ancestor of the new one. It runs over every match, and the first failure aborts the whole command (source).

Invariants: never creates or deletes a bookmark. Never rewrites a commit. Never moves backwards or sideways without -B. Either every match moves, or none does.

7. Errors and edge cases

MessageWhenSource
error: the following required arguments were not provided: <NAMES|--from <REVSETS>>Neither names nor --from were given. Clap exits with status 2.move.rs
No bookmarks to update.Nothing matched, or every match is already at the target. This is a status message, not an error.move.rs
Warning: No matching bookmarks for names: <names>Exact names that have no local bookmark.mod.rs
Refusing to move bookmark backwards or sideways: <name>, hint Use --allow-backwards to allow it.The first match that isn't a fast-forward. Nothing moves.move.rs
Warning: Target revision is empty.The target has no changes and no description.move.rs
Hint: Specify bookmark by name to update just one of the bookmarks.More than one bookmark moved and no names were given.move.rs
Failed to parse name pattern: …A name pattern doesn't parse.revset_util.rs

8. Worked example

From upstream's test_bookmark_move_matching. Bookmarks a1 and a2 sit on one branch. b1 and c1 sit on another, under @ (described head2). Then jj bookmark move --from=::@ moves every bookmark in @'s ancestry up to @:

jj bookmark move --from=::@ Before: from root, one branch has e8849ae1 with a1 and a2, then 9328ecc5. The other branch has c2934cfb with b1, then 2cbf6566 with c1, then @ 0dd9a4b1. After jj bookmark move --from=::@, b1 and c1 are both on @ 0dd9a4b1. a1 and a2 are not ancestors of @, so they stay. before after move --from=::@ root c2934cfb b1 2cbf6566 c1 @ 0dd9a4b1 head2 e8849ae1 a1 a2 9328ecc5 root c2934cfb 2cbf6566 @ 0dd9a4b1 b1 c1 e8849ae1 a1 a2 9328ecc5

Upstream's snapshot:

$ jj bookmark move --from=::@
Moved 2 bookmarks to vruxwmqv 0dd9a4b1 b1 c1 | (empty) head2
Hint: Specify bookmark by name to update just one of the bookmarks.

a1 and a2 aren't in ::@, so they aren't selected. The hint appears because two bookmarks moved and no names were given. In the same test, jj bookmark move '?1' selects a1, b1 and c1 by glob. It fails with Refusing to move bookmark backwards or sideways: a1, because a1's commit isn't an ancestor of @, and nothing moves at all.