jj / How jj tag track works

How jj tag track works

jj tag track links remote tags to the local tag of the same name. The remote target is merged into the local tag, creating it if needed, and later fetches keep the two in step.

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: it is jj bookmark track for tags. jj takes name@remote symbols or name patterns (with an optional --remote, default every remote except git), skips refs already tracked, and in one transaction calls track_remote_tag on each: a three-way merge of the remote target into the local tag, then the state becomes tracked. It records track remote tag <symbols>. Unlike the bookmark command, it doesn't list conflicted results afterwards.

The command is cmd_tag_track. Remote tags fetched by jj git fetch or jj git clone are tracked by default, as upstream's test shows, so this command is mainly for tags that were untracked (source). Its inverse is jj tag untrack.

1. Command-line surface

ArgumentTypeDefaultMeaning
TAG[@REMOTE] (positional)patterns or remote symbols, required—Name patterns (globs by default) or exact name@remote symbols, not both (source, parse_name_patterns_or_remote_symbols).
--remote REMOTEremote patterns, repeatableall remotes but gitWhich remotes the patterns apply to; not allowed with symbols (source).

TagTrackArgs

2. Configuration

None of its own. The reserved git remote is excluded from pattern matches unless --remote names it (source, default_ignored_remote_name).

3. End-to-end flow

jj tag track: call flow cmd_tag_track loads the workspace and splits its arguments into patterns and symbols, rejecting a mix. Symbols are resolved exactly; patterns match existing remote tags plus absent entries for local tags on matching remotes, with warnings for unmatched names and remotes. Already tracked refs are skipped with a warning. A transaction calls track_remote_tag for each, prints Started tracking N remote tags, and tx.finish records the operation. split args into tag patterns / tag@remote symbols; reject mixing, and --remote with symbols tag@remote symbols resolve_trackable_remote_tags; unknown → "No matching remote tags for names" tag patterns (+ --remote) trackable_remote_tags_matching: existing remote tags + absent ones for local tags already tracked → "Warning: Remote tag already tracked: …"; the rest are kept start_transaction; track_remote_tag(symbol) for each merge_local_tag(local, base = tracked_target (absent), other = remote target); state = Tracked "Started tracking N remote tags."; tx.finish(ui, "track remote tag <symbols>")

4. Core architecture touchpoints

StructureRead / writeHowExplained on
Remote tag RemoteRef.statewriteSet to Tracked (track_remote_tag).view
View.local_tagswriteThree-way merged with the remote target: created if absent, conflicted if both sides differ (merge_local_tag).view
Immutable setmay changeA newly created local tag adds its commit to tags().revsets
OperationwriteOne operation if anything was tracked; otherwise Nothing changed.operations

5. Transaction and operation

  • Transaction: always started; with nothing new to track, tx.finish prints Nothing changed. (source).
  • Operation description: track remote tag <name@remote, …> (source).
  • Commits: none created or rewritten.
  • jj undo restores the previous local target and tracking state.

6. Algorithm and invariants

Selection mirrors bookmarks exactly: resolve_trackable_remote_tags accepts a symbol if the remote tag exists, or a local tag of that name exists and the remote is known; trackable_remote_tags_matching adds absent placeholders for local tags missing on a matching remote. Tracking then merges with base tracked_target(), absent for an untracked ref, so a missing local tag is created, an equal one is unchanged, and a different one becomes conflicted (source).

Invariants: remote targets never change. Tracked refs are skipped with a warning, never re-merged.

7. Errors and edge cases

MessageWhenSource
Cannot specify both <tag> patterns and <tag>@<remote> symbolsMixed argument forms.track.rs
--remote cannot be used with <tag>@<remote> symbolsBoth given.track.rs
Warning: Remote tag already tracked: <symbol>Skipped.track.rs
Warning: No matching tags for names: … / No matching remotes for names: … / No matching remote tags for names: …Unknown names, remotes or symbols.mod.rs
Nothing changed.Nothing new to track.cli_util.rs

8. Worked example

From upstream's test_tag_track_untrack. A clone tracked tag1–tag4 from origin. Then tag2–tag4 were untracked (and local tag3 deleted), a local tag5 was created, and local tag4 was moved to a new commit. Now jj tag track 'tag[1345]' unknown:

jj tag track on tracked, untracked, conflicting and new tags tag1 is already tracked and is skipped with a warning. tag3, deleted locally, is recreated at the remote commit. tag4, moved locally, becomes conflicted between the local commit and the remote commit. tag5, local only, gets an absent tracked entry on origin, shown as not created yet. unknown matches nothing and is warned about. before after jj tag track 'tag[1345]' unknown tag1 local = @origin, tracked warning: already tracked tag3 deleted locally, @origin untracked tag3 recreated at @origin's commit tag4 local moved, @origin untracked tag4 (conflicted): local + remote tag5 local only @origin (not created yet), tracked unknown nowhere warning: no matching tags

Upstream's output (source):

Warning: No matching tags for names: unknown
Warning: Remote tag already tracked: tag1@origin
Started tracking 3 remote tags.