jj / How jj tag set works

How jj tag set works

jj tag set creates local tags, or moves existing ones with --allow-move, so they point at one revision. Because tags are in the default immutable set, tagging @ also makes jj start a fresh working-copy commit on top of it.

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 -r (default @). For each name, it refuses if the tag already exists and --allow-move isn't given, and counts new and moved tags. It warns if the target is empty. In one transaction every name is set to point at the target, jj prints Created N tags pointing to … and/or Moved N tags to …, and records set tag <names> to commit <id>. If @ became immutable, the transaction's finish adds a new empty working-copy commit on top.

The command is cmd_tag_set, also available as jj tag s (source). Unlike jj bookmark set, there is no fast-forward rule: an existing tag can be moved anywhere, but only on request. Tags are stored in the view next to bookmarks; see the view page.

1. Command-line surface

FlagTypeDefaultMeaning
NAMES (positional)tag names, required—Exact names, parsed as revset symbols, so names like foo@ need quoting (source, parse_tag_name).
-r, --revision, --to REVSETrevset@The target, exactly one commit (source).
--allow-moveboolfalseAllow moving tags that already exist (source).

TagSetArgs

2. Configuration

The default immutable_heads() includes tags(), so every tagged commit and its ancestors become immutable (source). That is what produces the new working-copy commit when @ is tagged. In a colocated workspace, the tag is exported to Git when the transaction finishes.

3. End-to-end flow

jj tag set: call flow cmd_tag_set loads the workspace and resolves the target revision. For each name, an existing tag without --allow-move is an error; otherwise new and moved tags are counted. An empty target warns. A transaction sets each tag to the target, prints the created and moved counts, and tx.finish records set tag. If the working-copy commit is now immutable, finishing creates a new commit on top and makes it the working copy. load workspace; target = resolve_single_rev(-r, default @) for each name: exists and no --allow-move → "Refusing to move tag" (nothing changes) count new (absent) and moved (pointing elsewhere); tags already there count as neither empty target → "Warning: Target revision is empty." start_transaction; set_local_tag_target(name, normal(target)) for every name "Created N tags pointing to …" / "Moved N tags to …" tx.finish(ui, "set tag … to commit …") write the operation; export to Git if colocated @ now immutable (it was tagged)? new empty commit on top becomes @: "The working-copy commit became immutable; a new commit …"

4. Core architecture touchpoints

StructureRead / writeHowExplained on
View.local_tagsread, writeEach name is set to RefTarget::normal(target). Unlike the bookmark setter, set_local_tag_target doesn't add the target to the view's heads (source, view).view
Immutable setreadThe new tag makes its commit immutable through tags() in immutable_heads().revsets
Working-copy commitmaybe createdA new empty commit on top of @ if @ was tagged (source).commits
OperationwriteOne operation.operations

5. Transaction and operation

  • Transaction: one, after every name passed the check (source).
  • Operation description: set tag <name1, name2, …> to commit <id>, listing every name given, including ones that didn't change (source).
  • Commits: none rewritten; possibly one new working-copy commit.
  • jj undo restores the previous tags and working copy.

6. Algorithm and invariants

Every name is checked before anything changes, so one refused name stops the whole command (source). A deleted tag counts as absent here. A source TODO notes that if tracked remote tags gain push support, deleted tags should count as present until pushed, as bookmark set already does. The counts only feed the status lines: a tag already at the target is still "set", but counted as neither new nor moved.

Invariants: after the command every named tag points exactly at the target, as a normal (non-conflicted) ref. Existing tags never move without --allow-move. No commit is rewritten.

7. Errors and edge cases

MessageWhenSource
Refusing to move tag: <name>, hint Use --allow-move to update existing tags.A named tag exists.set.rs
Warning: Target revision is empty.The target has no changes and no description.set.rs
Warning: The working-copy commit became immutable; a new commit has been created on top of it.@ was tagged.cli_util.rs
clap: invalid value 'foo@' for '<NAMES>...': Failed to parse tag name: Syntax errorA name that isn't a valid symbol; quote it ('foo@'). An empty name fails the same way.revset_util.rs, test
Warning: Failed to export some tags: foo@git: Ref cannot point to the root commit in GitA tag on the root commit, when exported to Git.test

8. Worked example

From upstream's test_tag_set_delete. After jj commit -mcommit1, jj tag set -r@- foo bar creates two tags. Then jj tag set foo baz fails, and jj tag set --allow-move foo baz moves foo and creates baz at @:

jj tag set --allow-move foo baz at @ Before: commit1 b876c5f4 has tags bar and foo, and @ bbc74930 is on top. After: foo and baz are on bbc74930, bar stays on commit1, and since bbc74930 is now immutable a new empty working-copy commit 13cbd515 is created on top of it. before after --allow-move foo baz root b876c5f4 commit1 bar foo bbc74930 @ (empty) root b876c5f4 commit1 bar bbc74930 baz foo (now immutable) 13cbd515 @ new working-copy commit

Upstream's output (source):

Warning: Target revision is empty.
Created 1 tags pointing to rlvkpnrz bbc74930 (empty) (no description set)
Moved 1 tags to rlvkpnrz bbc74930 (empty) (no description set)
Warning: The working-copy commit became immutable; a new commit has been created on top of it.
Working copy  (@) now at: yqosqzyt 13cbd515 (empty) (no description set)
Parent commit (@-)      : rlvkpnrz bbc74930 (empty) (no description set)