jj / How jj bookmark delete works

How jj bookmark delete works

jj bookmark delete removes the local target of matching bookmarks but keeps their remote-tracking bookmarks, so the next jj git push deletes the bookmark on the remote too. Commits are never touched.

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 matches the name patterns against local bookmarks, warns about exact names that don't exist, and prints No bookmarks to delete. if nothing matched. Otherwise, in one transaction, it sets each matched bookmark's local target to absent, prints Deleted N bookmarks., and records delete bookmark <names>. A bookmark with tracked remotes then shows as (deleted), and pushing propagates the deletion.

The command is cmd_bookmark_delete. Its help text points to jj abandon for removing the commits as well, and to jj bookmark forget when the deletion shouldn't reach the remotes (source). How pushes handle deleted bookmarks is on the git push page; bookmark storage is on the view page.

1. Command-line surface

ArgumentTypeDefaultMeaning
NAMES (positional)name patterns, required—Bookmarks to delete; globs by default, other string-pattern kinds allowed, several unioned (source, parse_union_name_patterns).

BookmarkDeleteArgs

2. Configuration

None of its own. Whether the deletion is later pushed depends on the bookmark being tracked on a remote, which remotes.<name>.auto-track-bookmarks and jj bookmark track control. In a colocated workspace, tx.finish exports the change to the Git repo, so the Git branch is removed too.

3. End-to-end flow

jj bookmark delete: call flow cmd_bookmark_delete loads the workspace, parses the name patterns, and collects matching local bookmarks, warning about exact names without one. With no matches it prints No bookmarks to delete. Otherwise a transaction sets each matched local target to absent, prints Deleted N bookmarks, and tx.finish records delete bookmark with the names. Remote-tracking bookmarks are not touched, so the next push deletes them on the remote. Shared lifecycle: load workspace and repo, snapshot the working copy (CLI page) union of name patterns → local_bookmarks_matching; warn on exact names with no local bookmark no matches → "No bookmarks to delete." set_local_bookmark_target(name, absent) for each "Deleted N bookmarks."; remote refs untouched tx.finish(ui, "delete bookmark <names>") colocated: exported to Git next jj git push: deletes it on tracked remotes

4. Core architecture touchpoints

StructureRead / writeHowExplained on
View.local_bookmarkswriteEach match is set to RefTarget::absent(), which removes the entry (source, set_local_bookmark_target).view
Remote bookmarksuntouchedTracked remote bookmarks keep their targets. A local name absent while a tracked remote exists is what jj shows as (deleted).view
Commits, headsuntouchedNothing is abandoned; the commits stay visible if anything else keeps them.commits
OperationwriteOne operation if anything matched.operations

5. Transaction and operation

  • Transaction: one, only when something matched (source).
  • Operation description: delete bookmark <name1, name2, …> (source).
  • Commits: none created or rewritten.
  • jj undo brings the local bookmarks back.

6. Algorithm and invariants

The patterns are unioned into one matcher and run over the local bookmarks only (source). So a bookmark that's already deleted, with only remote entries left, doesn't match, and deleting it again prints No bookmarks to delete.; upstream's test contrasts this with forget. The same bookmark matched by several patterns is deleted once. The unmatched-name warning covers only exact names (warn_unmatched_local_bookmarks).

Setting the local target to absent is all that happens. A tracked remote bookmark whose local counterpart is gone becomes a pending deletion: jj git push treats "local absent, remote tracked and present" as a deletion to send. A remote bookmark that is tracked but itself absent, as with bookmark track on a never-pushed name, has nothing to delete, so after deletion the name disappears entirely.

Invariants: only local bookmark entries change. No remote ref, commit or head is touched. Deletion happens for all matches or none.

7. Errors and edge cases

MessageWhenSource
No bookmarks to delete.No local bookmark matched. Not an error.delete.rs
Warning: No matching bookmarks for names: …An exact name has no local bookmark.mod.rs
Failed to parse name pattern: Invalid string patternA pattern doesn't parse, e.g. an unclosed glob class.revset_util.rs
clap: missing <NAMES>No names given.delete.rs

8. Worked example

From upstream's test_bookmark_delete_glob. Bookmarks bar-2, foo-1, foo-3 and foo-4 were pushed to origin, and foo-5 is tracked but never pushed. Then jj bookmark delete 'foo-[1-3]' prints Deleted 2 bookmarks., and jj bookmark delete foo-4 foo-* foo-* deletes the remaining two:

Bookmark state after jj bookmark delete Before: bar-2, foo-1, foo-3 and foo-4 are local and tracked on origin; foo-5 is local and tracked but absent on origin. After deleting foo-[1-3], foo-4 and foo-5: bar-2 is unchanged; foo-1, foo-3 and foo-4 are listed as deleted with their origin entries kept, ready to be deleted on push; foo-5 is gone entirely. before local @origin bar-2 8e056f6b 8e056f6b foo-1 8e056f6b 8e056f6b foo-3 8e056f6b 8e056f6b foo-4 8e056f6b 8e056f6b foo-5 8e056f6b tracked, absent after the deletes local @origin bar-2 8e056f6b 8e056f6b foo-1 (deleted) 8e056f6b foo-3 (deleted) 8e056f6b foo-4 (deleted) 8e056f6b foo-5: gone

Upstream's bookmark listing afterwards (source):

bar-2: qpvuntsm 8e056f6b (empty) commit
  @origin: qpvuntsm 8e056f6b (empty) commit
foo-1 (deleted)
  @origin: qpvuntsm 8e056f6b (empty) commit
foo-3 (deleted)
  @origin: qpvuntsm 8e056f6b (empty) commit
foo-4 (deleted)
  @origin: qpvuntsm 8e056f6b (empty) commit

Running jj bookmark delete 'foo-[1-3]' a second time prints No bookmarks to delete., because the local entries no longer exist.