Skip to main content
RunBook Academy

Git, CI/CD & GitOpsVIII · BranchingBranching

Tracking and upstream — what `@{u}` means and how branches are linked to remotes

Intermediate⏱ ~18 mingit

What you'll learn

  • Explain what a remote-tracking ref is and how it differs from a local branch
  • Configure an upstream with git branch --set-upstream-to and remove it with --unset-upstream
  • Read the @{u} and @{upstream} shorthands and use them in commands like git log, git diff, and git pull
  • Distinguish ahead/behind relationships from merge-base relationships in the tracking configuration
  • Recognise when a tracking configuration is stale or missing and how to repair it

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 word “tracking” in Git has a precise meaning that the word itself does not suggest. A tracking branch is not a special kind of branch; it is an ordinary local branch that has a configuration entry branch.<name>.remote and branch.<name>.merge pointing at a remote branch. The configuration is what makes git pull default to “merge from upstream”, git push default to “push to upstream”, and git status default to “compare against upstream”. Without the configuration, every push and pull requires explicit arguments. In production, the configuration is what lets the team run git push from a CI script without naming the remote and the branch every time.

What an upstream is, on disk

The upstream of a local branch is a configuration entry in .git/config (or one of Git’s higher-priority config files). It has three fields:

[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 upstream branch on that remote. Together, they describe a single relationship: “this local branch’s upstream is the branch feature/iam-rotation on origin”.

The local branch and the remote branch are otherwise independent Git objects. The local branch is a ref under refs/heads/. The remote branch’s local mirror is a ref under refs/remotes/, called a remote-tracking ref. The upstream configuration tells Git to compare and update these two refs.

git branch -vv
# * feature/iam-rotation   9f3c1d7 [origin/feature/iam-rotation] rotate iam keys
#   main                   8a3f9d2 [origin/main: behind 2] bump terraform module

The [origin/feature/iam-rotation] in brackets is the upstream. The colon-separated [origin/main: behind 2] adds the ahead/behind relationship: in this case, local main is behind the remote main by two commits.

git branch —set-upstream-to

The plumbing command for writing the upstream configuration is git branch --set-upstream-to=<upstream> [<branch>]. The <upstream> argument is a remote-tracking ref (e.g. origin/main), and <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
git branch --set-upstream-to=origin/feature/iam-rotation feature/iam-rotation

# Remove the upstream configuration
git branch --unset-upstream

--set-upstream-to writes the three fields above into the Git config. It does not touch the ref, does not move any commits, and does not contact the remote. It is purely a configuration write.

flowchart LR
    A["git branch --set-upstream-to=origin/main"] --> B["write .git/config"]
    B --> C["[[branch "main"]\nremote = origin\nmerge = refs/heads/main]"]
    C --> D["git pull / push / status\nuse this configuration"]

The most common case where --set-upstream-to is needed is when a local branch was created before the corresponding remote branch existed, or when a clone was done with --no-track. The newer git switch -c <branch> <remote-branch> and git checkout --track <remote-branch> automatically set the upstream; explicit --set-upstream-to is for the cases where the link is added after the fact.

The @{u} and @{upstream} shorthands

@{u} and @{upstream} are rev-parse shorthands that resolve to the upstream of the current branch. They are usable anywhere a commit-ish is expected:

# Show commits on the current branch that are not on the upstream
git log @{u}..
# 9f3c1d7 rotate iam keys
# 6f4e5a6 add new iam policy

# Show commits on the upstream that are not on the current branch
git log ..@{u}
# 4d2c8e0 bump terraform module
# 7e8f9a0 add cert renewal hook

# Diff the working tree against the upstream
git diff @{u}

# Diff the index against the upstream
git diff --cached @{u}

# Show the OID of the upstream tip
git rev-parse @{u}
# 8a3f9d2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e

The shorthand is shorthand for “the OID that branch.<current>.merge resolves to on the remote named by branch.<current>.remote”. If the current branch has no upstream configured, @{u} errors out:

git checkout feature/no-upstream
git log @{u}..
# fatal: no upstream configured for branch 'feature/no-upstream'

The @{u} syntax is most useful in scripts 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.

How tracking interacts with fetch, pull, and push

The three core operations interact with the tracking configuration in specific ways:

  • git fetch updates the remote-tracking refs under refs/remotes/<remote>/<branch> from the remote. It does not touch the local branches under refs/heads/. After a fetch, the remote-tracking ref may have moved, but the local branch has not.
  • git pull is git fetch followed by git merge (or git rebase, depending on configuration). The fetch updates the remote-tracking refs; the merge moves the local branch to incorporate the remote’s commits.
  • git push contacts the remote and updates the remote branch to match the local branch’s tip. With no arguments, git push uses the upstream configuration to know which remote and which branch to push to.
# Fetch updates remote-tracking refs only
git fetch origin
# remote: Counting objects: 5, done.
# From github.com:org/infra
#    8a3f9d2..4d2c8e0  main -> origin/main

# Pull fetches and merges the upstream into the local branch
git pull
# Updating 8a3f9d2..4d2c8e0
# Fast-forward

# Push sends the local branch to the upstream
git push
# To github.com:org/infra.git
#    9f3c1d7..a1b2c3d  feature/iam-rotation -> feature/iam-rotation

Each operation can also be run explicitly without the configuration:

git fetch origin feature/iam-rotation
git pull origin feature/iam-rotation
git push origin feature/iam-rotation

In production, scripts that orchestrate branches across multiple remotes should use the explicit form. The implicit form is for the interactive workflow of a single engineer on a single remote.

Repairing a stale or missing upstream

The three failure modes for tracking and how to repair them:

  • No upstream configured. A local branch has no upstream configuration because it was created without --track and no --set-upstream-to was run. git push and git pull error with “no upstream configured”. Fix with git push -u origin <branch> (the -u flag implicitly calls --set-upstream-to), or explicitly with --set-upstream-to.

  • Upstream deleted on the remote. The local branch’s configuration still names the remote branch, but the branch no longer exists on the remote. git fetch cannot update the remote-tracking ref; git pull errors with “couldn’t find remote ref”. Fix with git branch --unset-upstream and then git push -u origin <branch> if the branch should be re-created on the remote.

  • Wrong upstream. A local branch was configured to track the wrong remote branch (e.g. origin/main instead of origin/release/v1.4.0). git pull fetches the wrong commits. Fix with git branch --set-upstream-to=<correct> to overwrite the configuration.

# Diagnose: show the upstream configuration for the current branch
git config --get-regexp '^branch\.' 
# 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

# Reset the upstream
git branch --unset-upstream feature/iam-rotation
git branch --set-upstream-to=origin/feature/iam-rotation feature/iam-rotation

Production discipline

  1. Always configure an upstream on long-lived branches. A local branch without an upstream cannot be pushed or pulled by name; every interaction requires explicit arguments.
  2. Use git push -u origin <branch> for the first push. The -u flag sets the upstream in the same step as the push. This is the only time the upstream should be set implicitly; subsequent pushes should use the configuration.
  3. Treat “no upstream” errors as configuration drift. A branch that was working yesterday and fails today with “no upstream configured” indicates the branch’s configuration was lost (possibly by a re-clone or a config rewrite); the fix is --set-upstream-to, not a new clone.
  4. Use @{u} in scripts that need the upstream tip. The shorthand is portable across branch renames and remote additions; hard-coding origin/main in a script is brittle.

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) discusses tracking in the context of multi-source applications, where each source branch has its own upstream and the controller needs the relationship to decide which commits to sync.
  • 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. What does `git branch --set-upstream-to=origin/main` actually do?

  2. Q2. `@{u}` resolves to the upstream of the current branch and errors out if no upstream is configured.

  3. Q3. What three fields does the upstream configuration write, and what do they describe?

  4. Q4. Diagnose a CI job that fails on a branch that worked yesterday and recommend a fix.

    A CI job runs `git pull && git push` on a long-lived feature branch. Yesterday the job succeeded; today it fails with `fatal: Couldn't find remote ref 'refs/heads/feature/iam-rotation'`. The remote repository still has the branch visible in the GitHub UI. The on-call engineer is investigating.

Passing score: 75%. Answers are checked in this browser.