Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXX · RemotesRemotes

Adding and removing remotes — the lifecycle of a remote entry

Intermediate⏱ ~18 mingit

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

Not yet marked complete on this device.

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:

  • --tags configures 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 configured remote.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-to once 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 --push for 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. Use git remote remove <name> first, or pick a different name.
  • fatal: No such remote '<name>' — the name is not in the config. Run git remote (no arguments) to list configured names.
  • error: Could not remove <name>; it is not in the remote-tracking namespace — internal error from rename when the ref namespace cannot be rewritten. Usually a sign of a corrupted packed-refs file; check with git 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 add succeeds even when the URL is wrong.
  • Orphaned upstream configurations after remove — local branches still reference the removed name. Use --unset-upstream per branch, or re-add the remote and re-fetch.

Production discipline

  1. Verify before removing. git remote remove deletes the remote-tracking refs as well as the config. Confirm no local branch tracks the remote before removing it.
  2. Fetch after set-url. A URL change leaves the remote-tracking cache stale. The next fetch should be --prune so old refs that no longer exist on the new URL are cleaned up.
  3. Use set-branches for large monorepos. A remote with thousands of branches slows down fetch and fills the local refs namespace with refs nobody will use.
  4. 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.
  5. Treat remote add as a deliberate change. The command succeeds even when the URL is wrong. Pair it with git remote get-url and git ls-remote to confirm the entry points at the repository you think it does.

Cross-course references

  • GitOps with Argo CD - Part V (MultiSource) uses git remote add to register an additional repository as a source for a single Argo CD Application; 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 --push to 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 add to bring a private Terraform module source under the same version control discipline as the main repository.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer runs `git remote add upstream $URL` and then `git log origin/main`. What happens?

  2. 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.

  3. 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.

  4. 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.