Git, CI/CD & GitOpsXX · RemotesRemotes
Upstream relationships — the per-branch link to a remote
What you'll learn
- Locate the upstream configuration in .git/config and read branch.<name>.remote and branch.<name>.merge
- Set an upstream with git branch --set-upstream-to and the short form git branch -u
- Remove an upstream with git branch --unset-upstream and confirm @{u} errors afterward
- Predict how push, pull, status, and @{u} behave when an upstream is set, unset, or stale
- Diagnose "no upstream configured" failures and recommend the correct repair command
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 previous lessons covered what remotes and remote-tracking
refs are. This lesson covers the third piece of the puzzle: the
upstream configuration that links a local branch to a
remote-tracking ref. The upstream is a per-branch configuration
entry in .git/config; it is what makes git push, git pull,
git status, and @{u} work without arguments. Without the
upstream, every interaction between a local branch and its remote
counterpart requires explicit arguments.
What the upstream is, on disk
The upstream of a local branch is two configuration fields under
[branch "<name>"] in .git/config:
[branch "feature/iam-rotation"]
remote = origin
merge = refs/heads/feature/iam-rotation
The remote field is the name of the remote (typically
origin). The merge field is the fully qualified ref name of
the branch on that remote — refs/heads/..., not just the
short name. Together, the two fields describe a single
relationship: “this local branch’s upstream is the branch
feature/iam-rotation on origin”.
There is no [remote "..."] entry implied by the upstream
configuration; the upstream references a remote by name, and
the named remote must exist (i.e., the [remote "<name>"]
block must also be present). If the remote is renamed, every
upstream configuration that referenced it is silently rewritten by
git remote rename; if the remote is removed, every upstream
configuration that referenced it becomes orphaned.
# Read the upstream configuration for every local branch
git config --get-regexp '^branch\\..*\\.(remote|merge)$'
# branch.main.remote origin
# branch.main.merge refs/heads/main
# branch.feature/iam-rotation.remote origin
# branch.feature/iam-rotation.merge refs/heads/feature/iam-rotation
A branch with no upstream configured simply does not appear in
this output. The local branch still exists and still has a tip;
it just has no link to a remote. git push and git pull on
such a branch require explicit arguments; git status reports
“no upstream configured”; @{u} errors.
Setting the upstream with —set-upstream-to and -u
The plumbing command for writing the upstream configuration is
git branch --set-upstream-to=<upstream> [<branch>]. The
<upstream> argument is the name of a remote-tracking ref (e.g.
origin/main); <branch> is the local branch to configure
(defaults to HEAD).
# Configure the current branch to track origin/main
git branch --set-upstream-to=origin/main
# Configure a different branch explicitly
git branch --set-upstream-to=origin/feature/iam-rotation feature/iam-rotation
# Set upstream and push in one step (the implicit form used by CI and onboarding)
git push -u origin feature/iam-rotation
git push -u origin <branch> is the implicit form of
upstream configuration: it pushes the branch to the named remote
and then runs git branch --set-upstream-to=origin/<branch>
under the hood. This is what every onboarding script and CI
seed script uses because it folds “push the branch” and
“remember the upstream” into one command.
git branch -u <upstream> is a short alias for
--set-upstream-to. Both write the same two fields; the only
difference is verbosity.
# Short form
git branch -u origin/feature/iam-rotation
# Long form
git branch --set-upstream-to=origin/feature/iam-rotation
flowchart LR
A["git branch -u origin/main"] --> B["edit .git/config"]
B --> C["[[branch "main"]\nremote = origin\nmerge = refs/heads/main]"]
C --> D["git push with no args\nuses 'origin' + 'main'"]
C --> E["git pull with no args\nuses 'origin' + 'main'"]
C --> F["@{u} resolves to\nrefs/remotes/origin/main"]
C --> G["git status compares HEAD\nagainst refs/remotes/origin/main"]
The configuration is local to the clone. Two clones of the same repository can have the same local branch tracking different upstreams — the relationship is a property of the local clone, not of the branch or the commit.
The @{u} shorthand in detail
@{u} (long form @{upstream}) is a rev-parse shorthand that
resolves to the OID of the upstream of the current branch.
It is usable anywhere a commit-ish is expected: git log, git diff, git rev-parse, git rebase, and so on.
# Show commits on HEAD that are not on the upstream
git log @{u}..
# Show commits on the upstream that are not on HEAD
git log ..@{u}
# Diff the working tree against the upstream
git diff @{u}
# Diff the index against the upstream
git diff --cached @{u}
# Read the OID of the upstream tip
git rev-parse @{u}
# 8a3f9d2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e
# Read the previous tip of the upstream (its history, useful for diffs)
git rev-parse @{u}@{1}
If the current branch has no upstream configured, @{u} errors:
git checkout feature/no-upstream
git log @{u}..
# fatal: no upstream configured for branch 'feature/no-upstream'
The shorthand is most useful in scripts and aliases that need
to operate on “the upstream” without hard-coding its name. A
push script can use git push origin HEAD:@{u} to push the
current branch to its upstream; a status check can use git rev-parse @{u}@{1} to read the previous tip of the upstream
and detect divergence.
Removing the upstream with —unset-upstream
git branch --unset-upstream [<branch>] deletes the
[branch "<name>"] block from .git/config. The local branch
itself is unchanged; only the link to the remote is severed.
# Unset the upstream of the current branch
git branch --unset-upstream
# Unset the upstream of a named branch
git branch --unset-upstream feature/iam-rotation
After the unset, the branch behaves as if it had been freshly
created with no --track flag:
git pusherrors withfatal: The current branch <name> has no upstream branch.git pullerrors withfatal: There is no tracking information for the current branch.git statusreportsYour branch is based on '<remote>/<branch>', but the upstream is gone.(when the remote-tracking ref has also been pruned) orYour branch ... no upstream configured(when the ref still exists).@{u}errors withfatal: no upstream configured for branch ....
Unsetting is the right tool when the upstream relationship is wrong — for example, when a long-lived branch was tracking the wrong remote branch, or when the remote branch it tracked was renamed and the upstream configuration needs to be reset before a new upstream is configured.
When push, pull, status, and @{u} read the upstream
Four core commands interact with the upstream configuration:
git pushwith no remote and no refspec reads the upstream configuration to determine where to push. Withpush.default = simple(the default since Git 2.0), the push goes to the upstream branch on the upstream remote if the local branch name matches. Otherwise,git pusherrors with “no upstream configured”.git pullisgit fetchfollowed bygit merge(orrebase, depending on configuration). The fetch step targets the upstream remote; the merge step targets the upstream branch. With no upstream,git pullerrors.git statusreports “ahead by N”, “behind by N”, or “diverged” by comparingHEADto the upstream. With no upstream, the report is “no upstream configured” and no ahead/behind numbers are shown.@{u}resolves through the upstream configuration to the remote-tracking ref, and from there to an OID.
# Push with no args uses the upstream
git push
# To github.com:acme/infra.git
# 9f3c1d7..a1b2c3d feature/iam-rotation -> feature/iam-rotation
# Pull with no args uses the upstream
git pull
# From github.com:acme/infra
# 8a3f9d2..4d2c8e0
# Updating 8a3f9d2..4d2c8e0
# Fast-forward
# Status uses the upstream for ahead/behind
git status
# On branch main
# Your branch is up to date with 'origin/main'.
In production, scripts that orchestrate branches across multiple
remotes should not rely on these implicit forms. The explicit
form (git push origin <branch>, git pull origin <branch>) is
unambiguous; the implicit form is for the interactive workflow
of a single engineer on a single remote.
Repairing a missing or wrong upstream
Three failure modes and their fixes:
- No upstream configured. A branch was created without
--trackand no--set-upstream-towas run. Fix withgit push -u origin <branch>(the-uflag sets the upstream in the same step as the push), or withgit branch --set-upstream-to=origin/<branch> <branch>(the explicit form). - Upstream branch deleted on the remote. The configuration
still names the remote branch, but the branch no longer
exists.
git fetchcannot update the remote-tracking ref;git pullerrors with “couldn’t find remote ref”. Repair withgit branch --unset-upstreamfollowed by eithergit push -u origin <branch>(to re-create the branch on the remote) orgit branch --set-upstream-to=origin/<new-branch> <branch>(to track a renamed branch). - Wrong upstream. The configuration names the wrong remote
branch (e.g.
origin/mainwhen the intent wasorigin/release/v1.4.0). Fix withgit branch --set-upstream-to=origin/release/v1.4.0to overwrite the configuration in a single edit.
Production discipline
- Set the upstream at branch creation time. Use
git switch -c <branch> --track <remote>/<branch>(orgit checkout -b ...in older syntax) so the upstream is established before the first commit lands. A branch that grows up without an upstream is a branch that will surprise the engineer at push time. - Use
git push -u origin <branch>for the first push. The-uflag folds the upstream configuration into the push. After the first push,git pushwith no arguments works because the configuration was set. - Treat
@{u}as the canonical way to refer to the upstream tip. Hard-codingorigin/mainin a script is brittle to remote renames;@{u}is portable across renames and across clones. - Unset before re-setting when the upstream is wrong.
git branch --set-upstream-to=<new>overwrites the existing configuration, but it is clearer to unset first and then set, so the audit trail (in the shell history or in a script) shows both operations explicitly. - Add a CI lint that asserts every long-lived branch has a
valid upstream.
git rev-parse --verify @{u}exits non-zero if the upstream is missing. Catching this at CI time is cheaper than catching it at deploy time.
Cross-course references
- Ansible for Production Sysadmins - Part XXXVIII (Review)
uses
git branch --set-upstream-toto repair the tracking configuration on long-lived feature branches after the underlying remote branch is renamed. - GitOps with Argo CD - Part IV (AppSources) reads the
upstream configuration to decide which commits to sync; an
Argo CD
Applicationwhose source ref has no upstream will refuse to sync and log a configuration error. - Terraform for Production Sysadmins - Part XII (State) draws the analogy between upstream tracking and Terraform state locking: both are configuration that links a local artifact to a shared resource and ensures that updates propagate correctly.
Quiz
Knowledge check · 4 questions
Q1. Which two configuration fields define the upstream of a local branch, and what does each one contain?
Q2. `git push -u origin feature/iam-rotation` pushes the branch and sets the upstream configuration in a single command.
Q3. What does `git branch --unset-upstream feature/iam-rotation` do, and what changes in `git push`, `git pull`, `git status`, and `@{u}` afterward?
Q4. An engineer reports that `git push` on a long-lived feature branch fails with "no upstream configured" after a clean re-clone. Diagnose and recommend a fix.
An engineer rebuilt their local clone of the infrastructure repository. After cloning, they checked out the long-lived `feature/iam-rotation` branch (which exists on the remote) and tried to push their local commits. `git push` errors with `fatal: The current branch feature/iam-rotation has no upstream branch`. The engineer expected the upstream to be carried over from the previous clone.
Passing score: 75%. Answers are checked in this browser.