Git, CI/CD & GitOpsXX · RemotesRemotes
Adding and removing remotes — the lifecycle of a remote entry
What you'll learn
- Add a remote with git remote add and verify it appears in .git/config and git remote -v
- Remove a remote with git remote remove and confirm the remote-tracking refs are also deleted
- Rename a remote with git remote rename and observe how branch upstream configurations are rewritten
- Change a remote URL with git remote set-url and constrain the fetched branches with git remote set-branches
- Recognise that remote add and remove are local config operations that never touch the remote repository
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The lifecycle of a remote is four operations: add, change, rename, remove. All four are local config edits; none of them contact the remote repository, none of them push or pull anything, and none of them change a single commit in the history. The remote repository is unaffected by every command in this lesson; the only thing that changes is your local clone’s view of it.
git remote add
git remote add <name> <url> writes a [remote "<name>"] block
into .git/config. The <name> is a single token (conventionally
origin, upstream, or a short name like prod); the <url> is
any of the four schemes covered in the previous lesson.
# Add a remote named upstream pointing at the project's canonical repository
git remote add upstream https://github.com/kubernetes-sigs/example.git
# Add a remote named prod that holds the deployed copy of the code
git remote add prod git@git.internal:deploy/infra.git
# Verify both are configured
git remote -v
# origin git@github.com:acme/infra.git (fetch)
# origin git@github.com:acme/infra.git (push)
# prod git@git.internal:deploy/infra.git (fetch)
# prod git@git.internal:deploy/infra.git (push)
# upstream https://github.com/kubernetes-sigs/example.git (fetch)
# upstream https://github.com/kubernetes-sigs/example.git (push)
git remote add does not fetch. The remote is added to the config
but the local clone has no remote-tracking refs for it until you
run git fetch <name>. Until that first fetch, every command that
would have used those refs (status checks, diffs against
upstream, log against the remote) fails with “no such ref”.
The --tags and --fetch flags control what the first fetch
will retrieve:
--tagsconfigures the remote to import tags in addition to branches on every fetch. Without this flag,git fetch <remote>does not import tags from that remote by default (though the legacy default behaviour is to fetch tags from the configuredremote.origin).--fetch=<refspec>sets the default refspec the remote uses when no further refspec is given on the fetch command line.
# Add a remote that also imports tags on every fetch
git remote add --tags upstream https://github.com/kubernetes-sigs/example.git
# Add a remote with a non-default refspec
git remote add --fetch=+refs/heads/release/*:refs/remotes/upstream/release/* \
upstream https://github.com/kubernetes-sigs/example.git
flowchart LR
A["git remote add upstream $URL"] --> B["edit .git/config\nadd [remote "upstream"] block"]
B --> C["no network call"]
B --> D["no remote-tracking refs yet"]
C --> E["run git fetch upstream to populate refs/remotes/upstream/"]
A subtle but important point: git remote add will fail if the
name you give already exists. There is no --force flag; the
fix is either to choose a different name or to remove the old
remote first with git remote remove. Two remotes with the same
name cannot coexist, and the conflict must be resolved by editing
the config.
git remote remove
git remote remove <name> deletes the [remote "<name>"] block
from .git/config and deletes the remote-tracking refs under
refs/remotes/<name>/ from the local clone. It does not contact
the remote repository, does not delete the remote’s branches,
and does not affect local branches under refs/heads/.
git remote remove upstream
git remote -v
# origin git@github.com:acme/infra.git (fetch)
# origin git@github.com:acme/infra.git (push)
After the removal, any local branch whose upstream configuration
named upstream/<branch> becomes orphaned: the upstream
configuration still exists under [branch "<name>"], but
branch.<name>.remote no longer points at a remote that exists.
git fetch no longer touches those remote-tracking refs;
git pull errors with “no such remote”; @{u} on those branches
errors with “no upstream configured”.
The orphaned branch is recoverable in two ways:
- Re-add a remote with the same name, run
git fetch, and the remote-tracking refs return. The upstream configuration on the local branch will start working again because it references the name, not the URL. git branch --unset-upstream <name>to drop the upstream configuration, then re-establish it explicitly with--set-upstream-toonce a remote exists.
In production, removing a remote is a “I am sure I am not using
it” check. Before running git remote remove, the engineer
should run git config --get-regexp '^branch\\..*\\.remote' | grep $REMOTE_NAME to confirm no local branch still tracks the remote.
git remote rename
git remote rename <old> <new> rewrites the [remote "<old>"]
block in .git/config to [remote "<new>"] and rewrites every
branch.<name>.remote entry that referenced the old name to
reference the new name. The remote’s URL is unchanged; the
remote-tracking refs under refs/remotes/<old>/ are moved to
refs/remotes/<new>/.
# Rename the upstream remote
git remote rename upstream vendor
git remote -v
# origin git@github.com:acme/infra.git (fetch)
# origin git@github.com:acme/infra.git (push)
# vendor https://github.com/kubernetes-sigs/example.git (fetch)
# vendor https://github.com/kubernetes-sigs/example.git (push)
# Confirm branches that tracked upstream now track vendor
git config --get-regexp '^branch\\..*\\.remote' | grep vendor
# branch.feature/iam-rotation.remote vendor
The rename touches the remote name, the per-branch upstream
configuration, and the ref namespace. It does not touch the
remote repository, does not push anything, does not require
network access. After a rename, git fetch vendor works exactly
as git fetch upstream did before; the local branch’s upstream
configuration has been silently rewritten.
A rename can fail for two reasons: the old name does not exist, or the new name already exists. Both errors are caught at the config-rewrite step; the rename is atomic in the sense that a failure leaves the config in a consistent state.
git remote set-url and set-branches
git remote set-url <name> <new-url> replaces the URL of an
existing remote. The remote’s name and refspec are unchanged;
only the url field is rewritten.
# Move the prod remote from one host to another
git remote set-url prod git@git-new.internal:deploy/infra.git
There are two useful variations:
git remote set-url --push <name> <push-url>sets the push URL separately from the fetch URL. This is how a remote can be fetched from a read-only mirror and pushed to a writable primary.git remote get-url <name>(and--pushfor the push URL) is the read counterpart; it prints the configured URL without touching the config.
git remote set-branches <name> <branch>... replaces the
default refspec of the remote so that only the listed branches
are fetched. This is how a remote with thousands of branches can
be constrained to fetch only the few that matter.
# Fetch only main and release/* from the upstream remote
git remote set-branches upstream main 'release/*'
# Verify the refspec that was written
git config --get-regexp '^remote\\.upstream\\.fetch'
# remote.upstream.fetch +refs/heads/main:refs/remotes/upstream/main
# remote.upstream.fetch +refs/heads/release/*:refs/remotes/upstream/release/*
set-branches is additive: the new branches are appended to
(or replace) the existing refspec. To add a single branch while
keeping the existing fetch set, list both the new and the old.
To reset to the default +refs/heads/*:refs/remotes/<name>/*,
use git remote set-branches <name> '*'.
Common failure modes
Five things that go wrong when manipulating remotes and how to recognise each one:
fatal: remote <name> already exists— the name is taken. Usegit remote remove <name>first, or pick a different name.fatal: No such remote '<name>'— the name is not in the config. Rungit remote(no arguments) to list configured names.error: Could not remove <name>; it is not in the remote-tracking namespace— internal error fromrenamewhen the ref namespace cannot be rewritten. Usually a sign of a corrupted packed-refs file; check withgit fsck.fatal: unable to access/Could not resolve host— the URL is reachable but unresolvable from this network. The config was written; the failure is at fetch time, not at add time.git remote addsucceeds even when the URL is wrong.- Orphaned upstream configurations after
remove— local branches still reference the removed name. Use--unset-upstreamper branch, or re-add the remote and re-fetch.
Production discipline
- Verify before removing.
git remote removedeletes the remote-tracking refs as well as the config. Confirm no local branch tracks the remote before removing it. - Fetch after set-url. A URL change leaves the
remote-tracking cache stale. The next fetch should be
--pruneso old refs that no longer exist on the new URL are cleaned up. - Use
set-branchesfor large monorepos. A remote with thousands of branches slows down fetch and fills the local refs namespace with refs nobody will use. - Document the remote URL. A remote URL is operational
contract data; it should be in a script or a config
management template, not buried in a developer’s
.git/config. - Treat
remote addas a deliberate change. The command succeeds even when the URL is wrong. Pair it withgit remote get-urlandgit ls-remoteto confirm the entry points at the repository you think it does.
Cross-course references
- GitOps with Argo CD - Part V (MultiSource) uses
git remote addto register an additional repository as a source for a single Argo CDApplication; the URL is supplied by the GitOps control plane, not by the engineer. - Ansible for Production Sysadmins - Part XL (RepoMirror)
uses
git remote set-url --pushto push a mirror from a read-only primary to a writable secondary; the fetch URL stays pointed at the primary so the mirror stays read-only on pull. - Terraform for Production Sysadmins - Part XV (PrivateMod)
shows the same pattern with
git remote addto bring a private Terraform module source under the same version control discipline as the main repository.
Quiz
Knowledge check · 4 questions
Q1. An engineer runs `git remote add upstream $URL` and then `git log origin/main`. What happens?
Q2. `git remote remove upstream` deletes the remote entry, the remote-tracking refs under `refs/remotes/upstream/`, and any local branches that previously tracked upstream.
Q3. Explain the difference between `git remote rename origin upstream` and `git remote set-url origin $NEW_URL` in terms of which files and refs each command touches.
Q4. An engineer renamed the `origin` remote to `github` to make room for a new `origin` pointing at an internal Git server. After the rename, several CI jobs started failing. Diagnose and recommend a fix.
An engineering team is migrating from GitHub to an internal Gitea server. The migration script runs `git remote rename origin github` and then `git remote add origin git@gitea.internal:acme/infra.git` on every clone. The next CI run fails with `fatal: No such remote 'origin'` on a deploy step that runs `git push origin main`.
Passing score: 75%. Answers are checked in this browser.