6.6 KiB
Named Remotes and Multi-Remote Workflows
For pushing to a single remote (the common case), see references/PUSH.md. This file covers everything else: managing named remotes, selecting which remote to push to, tracking remote bookmarks, and the two canonical multi-remote setups.
Named remotes
When a repo has more than one remote (e.g. origin = your fork, upstream = the canonical source), you select the target with --remote <name>.
jj git remote list # See all remotes and their URLs
jj git remote add upstream <url> # Add a second remote
jj git remote rename origin fork # Rename a remote
jj git remote set-url origin <new-url> # Change a remote's URL
jj git remote remove old-name # Remove a remote (and forget its bookmarks)
Default remote selection: when --remote is omitted, jj git push uses:
- The
git.pushconfig setting (if set), or - The remote named
origin(if multiple remotes exist), or - The single remote (if only one exists)
There is no built-in option to push to multiple remotes in one command — call jj git push once per remote.
Pushing the same bookmark to two remotes
jj bookmark move my-feature --to @ # Move the bookmark to current change
jj git push -b my-feature --remote fork # Push to your fork
jj git push -b my-feature --remote upstream # Push to canonical (e.g. for a PR target branch)
Setting a default remote
To avoid typing --remote repeatedly:
# Per-repo (writes to .jj/repo/config.toml)
jj config set --repo git.push fork
# Globally (writes to ~/.config/jj/config.toml)
jj config set --user git.push origin
Tracking a remote bookmark
By default, when you jj git fetch, remote bookmarks appear as <name>@<remote> (e.g. main@upstream). To get a local bookmark that follows it:
jj bookmark track main@upstream # Track upstream/main as a local 'main'
jj bookmark track 'glob:release/*@upstream' # Track all upstream release branches
Once tracked, jj git push --tracked --remote upstream will push all tracked bookmarks back to that remote.
Fetching from a specific remote
jj git fetch --remote upstream # Fetch only from upstream
jj git fetch --all-remotes # Fetch from every configured remote
When a push is rejected
If the remote has moved since your last fetch, jj git push refuses (similar to git push --force-with-lease). Re-sync first:
jj git fetch --remote <name> # Pull the remote's current state
# (resolve any bookmark conflicts or rebase if needed)
jj git push -b <bookmark> --remote <name> # Try again
Quick reference
| Action | Command |
|---|---|
| List remotes | jj git remote list |
| Add a remote | jj git remote add <name> <url> |
| Push to specific remote | jj git push -b <bookmark> --remote <name> |
| Push all tracked bookmarks to a remote | jj git push --tracked --remote <name> |
| Set default push remote (repo) | jj config set --repo git.push <name> |
| Track a remote bookmark | jj bookmark track <name>@<remote> |
| Fetch one remote | jj git fetch --remote <name> |
| Fetch all remotes | jj git fetch --all-remotes |
Common multi-remote workflows
Two canonical setups documented in the jj docs. Pick the one that matches your relationship to the remotes; the config is one-time per repo.
Nomenclature (jj convention):
origin= a remote you have write access to (typically where you push)upstream= the more canonical / well-known repo (you may not have push access)- Trunk is assumed to be
main, so remote bookmarks aremain@originandmain@upstream
Scenario 1: GitHub-style fork contributing upstream
upstream is the canonical project; origin is your fork; you open PRs from origin against upstream.
Typical actions:
- Fetch from
upstreamto get the latest changes - Push
maintooriginto keep your fork'smainin sync - Push
my-featuretoorigin, then open a PR targetingupstream
One-time setup:
# Fetch from both remotes when you run `jj git fetch`
jj config set --repo git.fetch '["upstream", "origin"]'
# Default push target is your fork
jj config set --repo git.push origin
# Track both remote bookmarks named `main`
jj bookmark track main
# Make upstream's main the trunk (treated as immutable; rebases stop here)
jj config set --repo 'revset-aliases."trunk()"' main@upstream
Why these specifically:
- Tracking
main@upstreamkeeps your localmainupdated whenever you fetch from upstream - Tracking
main@originmeansjj git pushwill also update your fork'smain trunk() = main@upstreamprevents jj from rewriting commits that are already in the canonical repo
Scenario 2: Independent repository, periodically integrating from upstream
origin is a long-lived repository with its own divergent history; upstream is the source you occasionally pull changes from but don't contribute back to.
Typical actions:
- Fetch from
originto get the latest of your own work - Push bookmarks to
origin; merge PRs intomain@origin - Periodically fetch from
main@upstreamand merge/rebase/duplicate its changes intomain@origin
One-time setup:
# Fetch only from origin (typical case)
jj config set --repo git.fetch '["origin"]'
# Or fetch from both if you want upstream changes visible on every fetch:
# jj config set --repo git.fetch '["upstream", "origin"]'
# Default push target is origin
jj config set --repo git.push origin
# Track only origin's main — do NOT track upstream's main
jj bookmark track main --remote=origin
jj bookmark untrack main --remote=upstream
# Your origin defines the trunk (your main is the canonical line)
jj config set --repo 'revset-aliases."trunk()"' main@origin
Why these specifically:
- Tracking only
main@originkeepsmain@upstreamas a clearly separate reference you canrebase/mergefrom intentionally — it won't silently overwrite your localmain trunk() = main@origintreats your divergent line (not upstream's) as the immutable history boundary
Choosing between the scenarios
| Question | Scenario 1 | Scenario 2 |
|---|---|---|
| Do you push features back to upstream as PRs? | Yes | No |
Should fetching from upstream update your local main? |
Yes | No (you integrate manually) |
Is upstream/main immutable from your repo's perspective? |
Yes | No (you may rewrite when integrating) |
Is origin/main allowed to diverge from upstream/main? |
Minimally (just for syncing) | Yes (it's the point) |