jj / How jj bookmark rename works

How jj bookmark rename works

jj bookmark rename old new moves a local bookmark's target to a new name and carries its remote tracking over. Remote bookmarks themselves are not renamed: the old name becomes a deletion to push, and the new name is ready to be pushed.

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. Moving the tracking state
  8. --overwrite-existing
  9. 7. Errors and edge cases
  10. 8. Worked example

In short: jj refuses if old doesn't exist, or if new already exists without --overwrite-existing. In one transaction it points new at old's target and removes old. For every remote where old was tracked, it starts tracking new there, unless an untracked new already exists on that remote. It records rename bookmark old to new, then warns that the old remote bookmarks still need pushing.

The command is cmd_bookmark_rename. Tracking is explained on the bookmark track page and the deletion that follows on bookmark delete.

1. Command-line surface

ArgumentTypeDefaultMeaning
OLD (positional)bookmark namerequiredAn existing local bookmark. Parsed as a revset symbol, so quoting rules apply; not a pattern (source, parse_bookmark_name).
NEW (positional)bookmark namerequiredThe new name (source).
--overwrite-existingboolfalseAllow NEW to exist already; its target is replaced (source).

BookmarkRenameArgs

2. Configuration

None of its own. In a Git-backed repo the reserved git remote is excluded from all the tracking logic (source, default_ignored_remote_name). In a colocated workspace, tx.finish exports the renamed branch to Git.

3. End-to-end flow

jj bookmark rename: call flow cmd_bookmark_rename loads the workspace and reads old's local target, failing if absent. Without --overwrite-existing it fails if new exists. Renaming to the same name is then a no-op. In a transaction, with --overwrite-existing it first untracks new's tracked remotes. It sets new to old's target and old to absent. For each remote where old was tracked, it tracks new there, or warns if an untracked new already exists there. It warns about tracking dropped by the overwrite, records the operation, and then warns about old remote bookmarks not renamed and about existing tracked remote bookmarks of new. old absent → "No such bookmark"; new present without --overwrite-existing → "Bookmark already exists" old == new → return (no-op; only reachable with --overwrite-existing) start_transaction; --overwrite-existing: untrack new's tracked remote bookmarks first set_local_bookmark_target(new, old's target); set_local_bookmark_target(old, absent) for each remote where old is tracked (git excluded): new@remote present and untracked → warning, tracking dropped for that remote otherwise → track_remote_bookmark(new@remote) overwrite warnings; tx.finish(ui, "rename bookmark old to new") after: "Tracked remote bookmarks for bookmark old were not renamed." (+ push hint) without overwrite: "Tracked remote bookmarks for bookmark new exist." (+ untrack hint)

4. Core architecture touchpoints

StructureRead / writeHowExplained on
View.local_bookmarkswritenew gets old's target, conflicts included; old becomes absent (source).view
Remote bookmarks of newwriteMarked tracked on each remote where old was; with --overwrite-existing, first untracked (track/untrack_remote_bookmark).view
Remote bookmarks of olduntouchedStill tracked with the local name gone, so they show as old (deleted) until pushed.view
CommitsuntouchedOnly names move.commits
OperationwriteOne operation, unless it errors or is the no-op.operations

5. Transaction and operation

  • Transaction: one, after the checks (source).
  • Operation description: rename bookmark <old> to <new> (source).
  • Warnings after the commit: the "not renamed" and "exist" warnings are printed after tx.finish, so they refer to a rename that already happened (source).
  • jj undo restores both names and their tracking state.

6. Algorithm and invariants

Moving the tracking state

For each remote bookmark named old that is tracked, outside the git remote (source):

  • If it's present, remember that tracked remote copies of old exist, for the warning at the end.
  • If new@remote already exists and is untracked, skip it with a warning: tracking it would merge that unrelated remote bookmark into the local one.
  • Otherwise call track_remote_bookmark(new@remote), which merges the remote target into the local bookmark and marks it tracked. When new@remote doesn't exist yet, it's an absent but tracked entry, which makes the next jj git push create new on that remote.

--overwrite-existing

Before moving anything, every tracked remote bookmark of new is untracked so the transfer starts clean (source). Afterwards, for each remote where new was tracked and present but old wasn't tracked, jj warns Tracking of remote bookmark new@remote was dropped. (source).

Invariants: the commit new points to is exactly old's former target. Nothing changes on remotes until you push. The git pseudo-remote is never tracked or untracked here.

7. Errors and edge cases

MessageWhenSource
No such bookmark: <old>old has no local target.rename.rs
Bookmark already exists: <new>new exists and there's no --overwrite-existing. This includes renaming a bookmark to its own name: the existence check comes before the same-name check.rename.rs, test
Warning: Tracked remote bookmarks for bookmark <old> were not renamed., hint to push old then new, or jj git push --all --deletedold had tracked, present remote bookmarks.rename.rs
Warning: Tracked remote bookmarks for bookmark <new> exist., hint Run `jj bookmark untrack <new>` …new already has tracked, present remote bookmarks, without overwrite. Not an error, so that renaming back and forth works.rename.rs
Warning: The renamed bookmark already exists on the remote '<remote>', tracking state was dropped.An untracked new@remote exists where old was tracked.rename.rs
Warning: Tracking of remote bookmark <new>@<remote> was dropped.Overwriting dropped tracking that old didn't have.rename.rs

8. Worked example

From upstream's test_bookmark_rename. bremote was pushed to origin and is tracked. Then jj bookmark rename bremote bremote2:

Bookmark state across jj bookmark rename bremote bremote2 Before: bremote is local and tracked on origin. After the rename: bremote is deleted locally with its origin entry kept, tracked; bremote2 is local and tracked on origin but absent there. The next push of bremote2 adds it on origin; pushing bremote deletes the old one. before bremote local: commit-2 @origin: commit-2, tracked after the rename bremote local: (deleted) @origin: commit-2, tracked bremote2 local: commit-2 @origin: absent, tracked push bremote2: "[add to 1e76d54fcfce]" push bremote: deletes the old remote name

Upstream's output (source):

Warning: Tracked remote bookmarks for bookmark bremote were not renamed.
Hint: To rename the bookmark on the remote, you can `jj git push --bookmark bremote` first (to delete it on the remote), and then `jj git push --bookmark bremote2`. `jj git push --all --deleted` would also be sufficient.

Renaming back with jj bookmark rename bremote2 bremote prints Tracked remote bookmarks for bookmark bremote exist. as a warning, not an error. After restoring, jj git push --bookmark bremote2 reports bookmark: bremote2 [add to 1e76d54fcfce] (source).