Understanding `git fetch` and `git pull`
Illustrated git pull and git fetch commands.
A basic understanding of Git may be helpful for reading this note. For an introduction to core concepts such as commits, the working tree, HEAD, and branches, see my earlier note.
Understanding git fetch and git pull
Overview
This note explains what git fetch and git pull actually do, and how they relate to several core Git references:
- local branches
- remote-tracking branches
- upstream branches
- fetch refspecs
At a high level:
git fetchupdates the local repository’s record of the remote stategit pullfirst performs afetch, and then integrates the upstream branch into the current local branch
Thus, a plain
1 | git pull |
may be understood conceptually as:
1 | git fetch |
or, if rebase is configured:
1 | git fetch |
To understand these commands precisely, one must first distinguish between ordinary local branches and remote-tracking branches.
Local branches and remote-tracking branches
Assume that the repository has two local branches and one remote named origin:
1 | local branches: |
These names refer to different kinds of references:
main,dev: ordinary local branchesorigin/main,origin/dev: remote-tracking branches corresponding to branches on the remoteorigin
Thus:
mainis a local editable branchdevis a local editable branchorigin/mainis Git’s local record of where remotemainwas the last time the repository communicated withoriginorigin/devis Git’s local record of where remotedevwas the last time the repository communicated withorigin
A remote-tracking branch such as origin/main is still a local reference (pointer). It is not the remote branch itself; rather, it is Git’s local record of that remote branch.
Therefore, updating origin/main means updating the local record of remote main. It does not mean that the local branch main has moved.
Remote-tracking branches
A remote-tracking branch is Git’s local record of a branch on a remote repository. Such references are stored under refs/remotes/<remote>/....
Examples:
origin/mainorigin/dev
A remote-tracking branch:
- is stored locally in the repository
- is updated by commands such as
git fetchandgit pull - is distinct from an ordinary local working branch
Its purpose is to record where Git last observed the corresponding remote branch. It is therefore mainly used for inspection and integration, rather than direct development.
Upstream branches
A local branch may have an upstream branch. The term upstream branch does not denote a distinct kind of branch; it denotes a tracking relation between a local branch and its default counterpart.
In common practice, that counterpart is a remote-tracking branch.
This relation is used by commands such as git pull and git status.
Typical examples include:
- local
maintracksorigin/main - local
devtracksorigin/dev
In these cases, origin/main and origin/dev are remote-tracking branches, and they simultaneously serve as the upstream branches of local main and local dev, respectively.
This relation may be inspected with:
1 | git branch -vv |
For example:
1 | * dev 8f2c1ab [origin/dev] message... |
This indicates that:
- local
devhas upstreamorigin/dev - local
mainhas upstreamorigin/main
Therefore, when the current branch is local dev, a plain
1 | git pull |
usually means:
- fetch from the remote
- integrate
origin/devinto localdev
The upstream branch determines what is integrated into the current local branch. It does not, by itself, determine the complete set of references updated during fetch.
Fetch refspecs
The references updated by git fetch are governed by the remote’s fetch refspec.
It may be inspected with:
1 | git config --get-all remote.origin.fetch |
A common default is:
1 | +refs/heads/*:refs/remotes/origin/* |
This mapping specifies that every branch under the remote namespace refs/heads/* is fetched into the corresponding local remote-tracking namespace refs/remotes/origin/*.
Under this default configuration, git fetch origin usually updates:
origin/mainorigin/dev- other
origin/*remote-tracking branches
This explains why running git pull on local dev may still update origin/main during the fetch step.
However, updating origin/main still does not imply that local main has moved.
Semantics of git fetch
git fetch contacts a remote, downloads objects (commits, etc.) not yet present in the local repository, and updates the corresponding remote-tracking branches (such as origin/main and origin/dev).
1 | git fetch origin |
It does not:
- modify the current local branch
- modify the working tree
- stage changes for commit
- merge remote changes into the current branch
A typical result is the following:
1 | Before fetch: |
Thus, git fetch updates the local repository’s knowledge of the remote state, but does not modify the current local branch.
Semantics of git pull
git pull first executes git fetch, and then integrates the upstream branch into the current local branch. The integration step is performed by merge or rebase, depending on configuration.
1 | git pull |
In the common case where local dev tracks origin/dev, invoking git pull on branch dev is conceptually equivalent to:
1 | git fetch origin |
or, if rebase is configured:
1 | git fetch origin |
Two mechanisms should be distinguished carefully:
- the fetch step updates remote-tracking branches according to the fetch refspec
- the integration step applies only to the current branch and its upstream
Therefore, if both remote main and remote dev have advanced, then running git pull on local dev may yield:
1 | origin/main updated |
In particular, the movement of origin/main does not imply the movement of local main.
Useful inspection commands
The following commands expose complementary aspects of branch state:
1 | git branch -a |
git branch -a
Lists local branches and remote-tracking branches.
Example:
1 | * dev |
This indicates that:
devandmainare local branches- the current branch is
dev origin/devandorigin/mainare remote-tracking branches- the default branch of remote
originismain
git branch -vv
Shows each local branch together with its upstream and divergence status.
Example:
1 | * dev 9476dca [origin/dev] fix: vibing ... |
This indicates that:
- local
devtracksorigin/dev - local
maintracksorigin/main - local
mainis 41 commits behindorigin/main
git config --get-all remote.origin.fetch
Shows the fetch refspec used for origin.
Example:
1 | +refs/heads/*:refs/remotes/origin/* |
This indicates that fetch updates the local origin/* remote-tracking references for all branches on the remote.
git log --oneline --decorate --graph --all
Displays the commit graph together with branch and reference labels.
Example:
1 | * 9476dca (HEAD -> dev, origin/dev) fix: vibing ... |
This shows directly that:
HEADis attached to localdev- local
devandorigin/devcurrently point to the same commit origin/mainis ahead of localmain
Taken together, these commands reveal the existence, tracking relations, and relative positions of local branches, remote-tracking branches, and HEAD.