skip to content

What does the refspec +refs/heads/*:refs/remotes/origin/* mean in Git?

level: middleimportance: must knowfreq 42%

answer

  1. source colon destination
  2. two different namespaces
  3. the star matches on both sides
  4. the leading symbol means force
  5. stored as remote.origin.fetch

basics

~20 s

It is the default fetch rule: take every branch under refs/heads/ on the remote and store it locally under refs/remotes/origin/, with the leading plus allowing those local copies to be updated even when the change is not a fast-forward.

solid answer

~40 s

A refspec has the shape `[+]<src>:<dst>`. In `+refs/heads/*:refs/remotes/origin/*` the source is every branch on the remote, since branches live under `refs/heads/` there, and the destination is your local `refs/remotes/origin/` namespace, so the remote's `main` becomes your `refs/remotes/origin/main` — what you type as `origin/main`. The wildcard matches on both sides and substitutes the same text. The leading `+` means force: update the destination ref even when the new value is not a fast-forward of the old one, which is what lets your remote-tracking ref follow a branch the remote rewrote. The line is stored as `remote.origin.fetch` in `.git/config` and is what `git fetch origin` uses when given no explicit refspec. Remote-tracking refs are read-only pointers; you never commit on them.

code

console · 6 lines
console
$ git config --get-all remote.origin.fetch
+refs/heads/*:refs/remotes/origin/*
$ git rev-parse origin/main
1f3a9c2b7d4e5f60718293a4b5c6d7e8f9012345
$ git rev-parse refs/remotes/origin/main
1f3a9c2b7d4e5f60718293a4b5c6d7e8f9012345

go deeper

for a junior

Recognise that origin/main is a local copy of the remote's branch stored under refs/remotes, and that a refspec is the rule mapping their branches into that namespace.

for a middle

Read the refspec aloud confidently: source colon destination, wildcards matching on both sides, the leading plus allowing non-fast-forward updates of remote-tracking refs, all stored in remote.origin.fetch.

for a senior

Show you can shape refspecs deliberately — narrowing to named branches on huge repositories, adding extra fetch lines, and diagnosing a stale or unexpectedly missing remote-tracking ref from the configuration alone.

for a principal

Be able to justify repository-wide conventions on what clones fetch, weighing bandwidth and clone size on large repositories against the confusion of clones that see different subsets of branches.

## The grammar A refspec is `[+]<src>:<dst>`. For a fetch, `<src>` names refs as they exist on the remote and `<dst>` names where to write them in your repository. Both sides are full ref paths, and a `*` may appear once in each, matching the same text on both sides. The optional leading `+` means update the destination even if the change is not a fast-forward. Reading the default aloud: take every ref under `refs/heads/` on the remote, and write it under `refs/remotes/origin/` here, forcibly if necessary. ## Why the destination namespace exists Git keeps three kinds of refs distinct. Your branches live under `refs/heads/`, tags under `refs/tags/`, and copies of the remote's branches under `refs/remotes/<remote>/`. The separation is what makes fetching safe: a fetch can never move your `main`, because the refspec's destination is `refs/remotes/origin/main`, a different ref. `origin/main` is just the short spelling of that path, and it is read-only — checking it out gives you a detached HEAD, because it is a record of where the remote was at your last fetch, not a branch you own. ## What the plus sign is for Without `+`, a fetch would only update the destination ref if the new commit is a descendant of the old one. Remote branches do get rewritten — someone amends and force-pushes, or a branch is recreated — and after that the remote's `main` is no longer a descendant of what you last saw. The `+` lets your remote-tracking ref simply follow whatever the remote now says, which is correct: it is a mirror, not a history you are protecting. Your own branches keep their non-fast-forward protection because they are not the destination of this refspec. ## Where it lives and how it is used The refspec is stored as `remote.origin.fetch` in `.git/config`; `git config --get-all remote.origin.fetch` prints it, and a remote may carry several fetch lines. `git fetch origin` with no arguments applies the configured refspec. Giving an explicit refspec on the command line, such as `git fetch origin refs/heads/main:refs/remotes/origin/main`, overrides it for that invocation. ## Narrowing or extending it Because it is only config, you can shape it. `git remote add -t main -t release upstream <url>` writes one narrow fetch line per named branch instead of the wildcard, so you never download branches you do not care about. `git remote set-branches [--add] <name> <branch>...` rewrites or extends that list later, and `git config --add remote.origin.fetch <refspec>` appends a line by hand. `--no-tags` at add time, recorded as `remote.<name>.tagOpt`, stops tags from being fetched automatically. Narrowing is a real technique on repositories with thousands of branches. ## Mapping into a different namespace Nothing forces the destination to mirror the source layout. A refspec can land refs anywhere under `refs/`, which is how mirror configurations map `+refs/*:refs/*`. The rules are only that the destination must be a valid ref path and that a wildcard on one side requires one on the other. ## The same grammar elsewhere Push uses the identical `<src>:<dst>` shape with the sides reversed in meaning — local source, remote destination — which is worth knowing so the syntax does not look like two unrelated features. The details of push behaviour are their own subject. ## Why this question separates candidates Anyone can say `origin/main` is the remote branch. Reading the refspec out loud shows you understand that Git models a remote as a mapping rule over namespaces — exactly the model you need when a fetch does not bring what you expected, when a remote-tracking ref looks stale, or when you want to fetch only part of a large repository.

  • What breaks if you drop the leading plus from the fetch refspec?
    Updates to remote-tracking refs then have to be fast-forwards. As soon as someone rewrites and force-pushes a branch, the remote's new tip is not a descendant of what you have, so the fetch refuses to update that ref and origin/<branch> silently stays behind until you force the fetch explicitly.
  • How would you configure a remote to fetch only the main branch of a huge repository?
    Add it narrowed: git remote add -t main upstream <url>, which writes fetch = +refs/heads/main:refs/remotes/upstream/main. On an existing remote, git remote set-branches upstream main rewrites the list and set-branches --add appends another branch. Adding --no-tags also stops automatic tag downloads.
  • Why can you not commit on origin/main?
    Because it is a remote-tracking ref under refs/remotes/, not a branch under refs/heads/. Checking it out detaches HEAD, so commits you make there belong to no branch, and the next fetch simply overwrites the ref with the remote's current tip. Create a local branch from it to work.

It is a mail-forwarding rule: anything addressed to a branch at their office gets re-filed into your own drawer labelled origin, replacing the previous copy.

saying these in an interview costs you the question

  • Reads the refspec as destination first, source second
  • Thinks the plus means include tags as well
  • Believes origin/main is a normal local branch
  • Says fetching can move your own main branch
  • Cannot say where the refspec is configured

context