In short: add writes a [remote] section with the standard fetch refspec and creates
an empty remote in the view. remove deletes the section, the branch sections that point at it,
the remote's Git refs and its remote bookmarks and tags. rename does the same but moves
everything to the new name, and rewrites trunk() and remotes.<old> settings
in the repo config. set-url only edits the Git config and records no operation;
list only reads it.
The group is RemoteCommand. Its doc line
still says the Git repo is a bare repo inside .jj/, which is only true for non-colocated
workspaces. Remote bookmarks and tags are described on the view
page; fetching and pushing are jj git fetch
and jj git push.
1. Command-line surface
| Subcommand | Arguments | Notes |
|---|---|---|
add | REMOTE URL [--push-url URL] | Local paths are made absolute (source). |
list | none | Prints NAME URL, or NAME URL (push: PUSH_URL) when they differ, and <no URL> for a missing one (source). |
remove | REMOTE | "Remove a Git remote and forget its bookmarks" (source). |
rename | OLD NEW | (source) |
set-url | REMOTE [URL] [--push URL] [--fetch URL] | The positional URL is short for --fetch; the two conflict. A URL kind not given is left alone, so this can't remove a URL (source). |
2. Configuration
jj reads remotes from the Git repo's config (.git/config, or
.jj/repo/store/git/config when not colocated) and writes back only that file's own
sections, so remotes defined in global Git config are seen but never rewritten there
(save_git_config). A remote counts as existing only if
it has a fetch or push URL; an empty [remote "x"] section is ignored
(try_find_active_remote).
jj's own repo config holds per-remote settings (remotes.<name>.*) and possibly
trunk(), which jj git clone and jj git init write. rename and
remove keep those in step (source).
3. End-to-end flow
add
The URLs go through absolute_git_url, then
git::add_remote validates the name, refuses an existing
remote, writes url, pushurl and fetch = +refs/heads/*:refs/remotes/<name>/*,
and calls ensure_remote so the view has the remote even before any fetch. jj then warns if
another remote already uses exactly the same fetch or push URL, without printing the URL in case it
holds credentials (warn_if_remote_url_matches).
remove
git::remove_remote removes the [remote]
section and every [branch] section whose remote or pushRemote is this
remote, deletes refs/remotes/<name>/*, jj's fetch refs refs/jj/remote-bookmarks/<name>/*
and refs/jj/remote-tags/<name>/* in Git
(remove_remote_git_refs), then removes the remote from the view with all its remote bookmarks and tags, and its entries in
git_refs. Local bookmarks are kept. The CLI then cleans the repo config
(source).
rename
git::rename_remote validates the new name, requires the
old remote to exist and the new one not to, and requires the standard configuration: exactly one fetch
refspec, the default one, and no push refspecs. It writes the section under the new name with a new
refspec, points matching [branch] sections at the new name, removes the old section, renames
the Git refs in all three namespaces (source; reflog message renamed remote OLD to NEW), and moves the remote view and
git_refs.
set-url
git::set_remote_urls does nothing if no URL is given,
otherwise replaces the given URLs and saves the config. It looks the remote up without applying Git's
URL rewrite rules, so it edits what is written in the config.
4. Core architecture touchpoints
- Git config: edited with
gixand saved back to the repo's own config file. Asave_remotehelper adds an empty section first to work around a gitoxide bug (source). - Git refs: remote-tracking refs are deleted or renamed in one ref transaction, with
each edit checking the old value (
add_ref,remove_ref). - View:
ensure_remote,remove_remote,rename_remoteact on the per-remote map of bookmarks and tags; see the view page. - Repo config:
trunk()andremotes.<name>tables (source).
5. Transaction and operation
| Subcommand | Operation | Source |
|---|---|---|
add | add git remote <name> | add.rs |
remove | remove git remote <name>, only if the view changed | remove.rs |
rename | rename git remote <old> to <new>, only if the view changed | rename.rs |
list, set-url | none | list.rs, set_url.rs |
Only the view changes are part of the operation. The Git config, the Git refs and the repo config are
edited directly, so jj undo after remove or rename brings back the
remote bookmarks in jj but not the remote in Git. For a remote named git, the view isn't
touched (its name is reserved for NAME@git), so no operation is recorded and no
Nothing changed. is printed (source).
6. Algorithm and invariants
Remote names
validate_remote_name applies Git's own rules, then rejects
git, reserved for the backing repo's refs (main@git), and any name with a slash,
because jj splits refs/remotes/REMOTE/NAME at the first slash
(parse_git_ref). add, rename's
new name and set-url are validated; remove and rename's old name aren't,
so a remote named git that came from Git can still be removed or renamed away, but not
back (test).
Repo config
On rename, the [remotes.OLD] table becomes [remotes.NEW], and a
trunk() alias of the form NAME@OLD becomes NAME@NEW, printing
Updating the revset alias `trunk()` to `…`. On remove, the table is deleted and such a
trunk() is deleted too, printing Resetting the revset alias `trunk()` to default value.
Only a trunk() that parses as a single remote symbol is touched
(source).
Invariants: after remove, no Git ref, view entry or git_refs entry
for the remote remains. After rename, the remote's fetch refspec, Git refs, view entry and
repo-config settings all use the new name. Nonstandard remotes are refused rather than half-converted.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
No git remote named '<name>' | remove, rename or set-url on an unknown remote. | git.rs, test |
Git remote named '<name>' already exists | add or rename to an existing name. | test |
Git remote named 'git' is reserved for local Git repository | Adding, renaming to or setting URLs on git. | git.rs, test |
Git remotes with slashes are incompatible with jj: <name> | A name containing /. | git.rs |
Git remote named '<name>' has nonstandard configuration | rename with a custom refspec or push refspec; remove/rename with unknown keys in the remote or branch sections. | git.rs |
Unexpected Git error when managing remotes | For example, an unparsable URL in the config (also breaks list). | test |
Warning: Remote <other> already uses the same URL., hint to remove the new one | add with a URL another remote has. | add.rs |
8. Worked example
From upstream's test_git_remote_rename_updates_trunk. A repo cloned
with jj git clone --branch=main remote local has the remote origin, the remote
bookmark main@origin, and a repo config with trunk() set to
main@origin and fetch-bookmarks under [remotes.origin]. Then
jj git remote rename origin upstream moves all of it:
Upstream's output, and trunk() still resolving afterwards (source):
$ jj git remote rename origin upstream
Updating the revset alias `trunk()` to `main@upstream`.
$ jj log -r 'trunk()' -T description
◆ init
│
~
The fetch-bookmarks entry comes from cloning with --branch; see
jj git clone. That the bookmark was tracked
follows from git.track-default-bookmark-on-clone; the test itself only checks
trunk().