Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXX · RemotesRemotes

Upstream relationships — the per-branch link to a remote

Intermediate⏱ ~20 mingit

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

Not yet marked complete on this device.

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 push errors with fatal: The current branch <name> has no upstream branch.
  • git pull errors with fatal: There is no tracking information for the current branch.
  • git status reports Your branch is based on '<remote>/<branch>', but the upstream is gone. (when the remote-tracking ref has also been pruned) or Your branch ... no upstream configured (when the ref still exists).
  • @{u} errors with fatal: 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 push with no remote and no refspec reads the upstream configuration to determine where to push. With push.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 push errors with “no upstream configured”.
  • git pull is git fetch followed by git merge (or rebase, depending on configuration). The fetch step targets the upstream remote; the merge step targets the upstream branch. With no upstream, git pull errors.
  • git status reports “ahead by N”, “behind by N”, or “diverged” by comparing HEAD to 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 --track and no --set-upstream-to was run. Fix with git push -u origin <branch> (the -u flag sets the upstream in the same step as the push), or with git 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 fetch cannot update the remote-tracking ref; git pull errors with “couldn’t find remote ref”. Repair with git branch --unset-upstream followed by either git push -u origin <branch> (to re-create the branch on the remote) or git 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/main when the intent was origin/release/v1.4.0). Fix with git branch --set-upstream-to=origin/release/v1.4.0 to overwrite the configuration in a single edit.

Production discipline

  1. Set the upstream at branch creation time. Use git switch -c <branch> --track <remote>/<branch> (or git 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.
  2. Use git push -u origin <branch> for the first push. The -u flag folds the upstream configuration into the push. After the first push, git push with no arguments works because the configuration was set.
  3. Treat @{u} as the canonical way to refer to the upstream tip. Hard-coding origin/main in a script is brittle to remote renames; @{u} is portable across renames and across clones.
  4. 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.
  5. 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-to to 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 Application whose 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

  1. Q1. Which two configuration fields define the upstream of a local branch, and what does each one contain?

  2. Q2. `git push -u origin feature/iam-rotation` pushes the branch and sets the upstream configuration in a single command.

  3. Q3. What does `git branch --unset-upstream feature/iam-rotation` do, and what changes in `git push`, `git pull`, `git status`, and `@{u}` afterward?

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