git fetch
TL;DR: The git fetch command safely downloads new commits, files, and branches from a remote repository (like GitHub) to your local computer. It updates your local copy of remote-tracking branches (e.g., origin/main) without modifying your local working files or index. It is a non-destructive, read-only operation designed to update your local cache before merging.
What Does git fetch Actually Do?
To understand git fetch, we must look at how Git splits network synchronization from history integration. Many version control systems automatically download and merge updates into your working files in a single step. Git does not. Instead, it maintains a strict boundary between:
- Downloading the history (network communication via
git fetch). - Integrating the history (local merges or rebases via
git mergeorgit rebase).
When you work on a project with a team, your local repository behaves like a sandbox. Your coworkers are pushing new code to the central server, and your local repository is unaware of these changes until you explicitly ask.
git fetch is your request to update the local database. It contacts the remote host defined via /git-remote, checks which commits have been added on the server since your last sync, downloads those database objects, and updates your remote-tracking branches.
+----------------------------------------+
| Remote Repo (e.g. GitHub) |
| main branch points to [Commit: C] |
+----------------------------------------+
|
| git fetch (Downloads Commit B & C)
v
+---------------------------------------------------------------------------+
| Local Repository |
| |
| Objects: Downloads Commits, Trees, and Blobs into .git/objects/ |
| |
| Refs: |
| Local Main: refs/heads/main ----------------------------> [Commit: A] |
| Remote-Tracking: refs/remotes/origin/main --------------> [Commit: C] |
| |
| HEAD points to refs/heads/main (Your working directory is on A) |
+---------------------------------------------------------------------------+
A remote-tracking branch (such as origin/main) is a cached pointer representing the state of the remote branch the last time you communicated with the server. Remote-tracking branches are read-only; you cannot write commits directly to them.
Because git fetch only updates these remote-tracking pointers and downloads the raw database objects, it is 100% safe. It will never overwrite your local uncommitted changes, cause conflicts, or modify your current development branch.
If you want to bring those changes into your active branch, you must follow the fetch with a merge or rebase, or run the combination command /git-pull.
What Happens Inside .git/ (Hands-on exploration)
Let’s look at the mechanics of git fetch inside the .git/ folder using a step-by-step walk-through.
Step 1: Establish the Baseline Local State
We will start by initializing a local repository, configuring a remote, and committing a file.
$ mkdir fetch-demo
$ cd fetch-demo
$ git init
Initialized empty Git repository in /home/admin/Code/fetch-demo/.git/
$ git remote add origin https://github.com/octocat/hello-world.git
At this stage, .git/refs/ has no remote-tracking branches:
$ find .git/refs/
.git/refs/
.git/refs/tags
.git/refs/heads
Step 2: Simulate the Remote State
Since we don’t want to make actual calls to GitHub in this demo, we will simulate what Git does during a fetch when the remote server has commits that we do not have.
Suppose the remote repository has a branch named main that contains a commit we don’t have:
- Commit Hash:
b86c4f74d081f9b32e185c8f1e63a152e46d84f8 - Tree Hash:
d9c0e5a6a3b2b4e8c10972b226e6d198305c48b1 - Author: Octocat <octocat@github.com>
- Message: “Update README.md”
During a fetch, Git performs a network negotiation where the server packages the new commit, tree, and blob files into a compressed packfile and sends it over the wire. This packfile is written into .git/objects/pack/.
Let’s simulate the download by manually creating this commit object in our local object database. We will use git hash-object to write the commit object:
# Create the text of our commit object
$ COMMIT_CONTENT=$(printf "tree d9c0e5a6a3b2b4e8c10972b226e6d198305c48b1\nauthor Octocat <octocat@github.com> 1774345200 +0000\ncommitter Octocat <octocat@github.com> 1774345200 +0000\n\nUpdate README.md")
# Write it directly into the Git object database
$ echo "$COMMIT_CONTENT" | git hash-object -w --stdin -t commit
b86c4f74d081f9b32e185c8f1e63a152e46d84f8
Let’s verify that this object is present in .git/objects/:
$ find .git/objects/ -type f
.git/objects/b8/6c4f74d081f9b32e185c8f1e63a152e46d84f8
Git has successfully created a loose object in the database! We can read it using the plumbing command git cat-file:
$ git cat-file -t b86c4f74d081f9b32e185c8f1e63a152e46d84f8
commit
$ git cat-file -p b86c4f74d081f9b32e185c8f1e63a152e46d84f8
tree d9c0e5a6a3b2b4e8c10972b226e6d198305c48b1
author Octocat <octocat@github.com> 1774345200 +0000
committer Octocat <octocat@github.com> 1774345200 +0000
Update README.md
Step 3: Update the Remote-Tracking Pointer
The second step of git fetch is updating the reference file representing the remote branch. Git creates the directory structure under .git/refs/remotes/origin/ and writes the commit hash into a file named main.
Let’s simulate this:
$ mkdir -p .git/refs/remotes/origin
$ echo "b86c4f74d081f9b32e185c8f1e63a152e46d84f8" > .git/refs/remotes/origin/main
Now let’s check our refs structure:
$ find .git/refs/
.git/refs/
.git/refs/tags
.git/refs/heads
.git/refs/remotes
.git/refs/remotes/origin
.git/refs/remotes/origin/main
Step 4: Inspect the Results using git log and git branch
We have successfully simulated a fetch. Let’s see how Git’s user-facing commands view the repository state now.
First, let’s list our branches. By default, git branch only shows local branches (which is empty right now). We must use the -r (remote) or -a (all) flags to view remote-tracking branches (controlled by /git-branch):
$ git branch -r
origin/main
Let’s run a git log on our remote branch (controlled by /git-log):
$ git log origin/main --oneline
b86c4f7 Update README.md
Note that our local workspace has no files checked out. If we check our current local directory:
$ ls -la
total 12
drwxr-xr-x 3 admin admin 4096 Jul 7 10:23 .
drwxr-xr-x 4 admin admin 4096 Jul 7 10:23 ..
drwxr-xr-x 7 admin admin 4096 Jul 7 10:23 .git
There is no README.md file in our directory. Even though the commit and its file data are safely downloaded inside the .git/objects/ database, they have not been unpacked into our working directory.
To merge this change, we would run git merge origin/main (detailed in /git-merge), which would update our local branch pointer and check the files out into our workspace.
Detailed Command Options & Advanced Usage
git fetch provides several options to optimize network requests, clean up stale references, and manage large-scale repositories.
1. Cleaning Up Stale References: git fetch --prune
As projects evolve, team members delete branches from the central server (e.g., after merging a Pull Request). However, by default, git fetch only adds new references—it never deletes references from your local .git/refs/remotes/ folder.
Over time, your local environment will accumulate dozens of “stale” remote branches that no longer exist on the server. To solve this, use the --prune (or -p) flag:
git fetch --prune origin
This tells Git: “Connect to origin, download new commits, and delete any files inside .git/refs/remotes/origin/ that represent branches that have been deleted on the server.”
To make this the default behavior for all fetches, you can configure it globally:
git config --global fetch.prune true
2. Fetching from All Remotes: git fetch --all
If you are working on a fork and have multiple remotes configured (like origin and upstream), fetching from them one by one is tedious:
git fetch origin
git fetch upstream
You can update all configured remotes simultaneously using the --all flag:
$ git fetch --all
Fetching origin
Fetching upstream
3. Shallow Fetching: git fetch --depth=<n>
For extremely large repositories (e.g., monorepos containing gigabytes of history), running a full fetch can take a long time and consume massive bandwidth.
If you only need to run tests or build a quick patch, you can perform a shallow fetch. This fetches only a specific number of recent commits, truncating the historical chain:
git fetch --depth=1 origin
This command downloads the latest commit and instructs Git to write the cutoff commit hash into a special file named .git/shallow. This file tells the revision walker not to look past those points, keeping the database footprint minimal.
Real-World Scenario: Safe Code Review Flow
A colleague has pushed a new feature branch named feature-payment-gateway to GitHub. They want you to review their code. You do not want to merge their code into your local branches yet, nor do you want to lose your current uncommitted changes.
Here is how you use git fetch to review their branch safely.
Step 1: Stash or Commit Your Work
Before changing branches, ensure your workspace is clean. You can stash your changes:
$ git stash
Saved working directory and index state WIP on main...
Step 2: Fetch the Remote State
Run git fetch to get your local database up to speed:
$ git fetch origin
From github.com:company/project
* [new branch] feature-payment-gateway -> origin/feature-payment-gateway
Step 3: Inspect the Commits Without Checking Out
Use git log to see what commits your colleague made, comparing their branch against your current main branch:
$ git log main..origin/feature-payment-gateway --oneline --graph
* 4f3a2b1 Implement Stripe API client
* a1e2d3c Add payment form UI layout
Step 4: Checkout the Remote Branch Safely
You can check out their tracking branch directly to inspect the code and run tests locally:
$ git checkout origin/feature-payment-gateway
Note: switching to 'origin/feature-payment-gateway'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them...
Since you checked out origin/feature-payment-gateway, you are in a detached HEAD state. You can browse the files, run compilers, and execute unit tests. If you make experimental changes, they will not affect your main branch.
Step 5: Return to Your Work
Once the review is complete, switch back to your branch and restore your stashed work:
$ git checkout main
$ git stash pop
Common Pitfalls and Mistakes
1. Assuming git fetch Updates Your Code
- The Problem: A developer runs
git fetch originand expects to see their files in VS Code update. When they don’t, they assume the command failed. - The Cause:
git fetchdoes not touch the index or the working directory. It only writes updates to the object database and the.git/refs/remotes/folder. - The Fix: Run a merge command after fetching:
Or rungit merge origin/maingit pullif you prefer to automate both steps in one command.
2. Accidental “Detached HEAD” Commits on Remote branches
- The Problem: You run
git checkout origin/feature-branch, make some changes, and commit them. Later, you checkout another branch and find your changes have disappeared. - The Cause: Because
origin/feature-branchis a remote tracking branch, checking it out puts you in a Detached HEAD state. Any commits you make are not attached to a local branch and will eventually be garbage collected. - The Fix: Never develop directly on
origin/branch. If you want to work on a remote branch, create a local tracking branch first:git checkout -b feature-branch origin/feature-branch
3. Ghost Branches in the Log
- The Problem: You run
git branch -rand see dozens of branches that were deleted on GitHub weeks ago. - The Cause: Regular fetches do not prune deleted references.
- The Fix: Run
git fetch --pruneto clean up the stale remote-tracking references.
Command Quick Reference
| Command | Action | Network Activity |
|---|---|---|
git fetch | Fetches updates from the default remote (origin). | Yes |
git fetch <remote> | Fetches updates from a specific named remote. | Yes |
git fetch --all | Fetches updates from all configured remotes. | Yes |
git fetch --prune | Fetches and deletes local references to remote branches that were deleted on the server. | Yes |
git fetch --dry-run | Shows what branches/tags would be updated without writing changes. | Yes |
git fetch --depth=<n> | Performs a shallow fetch, limiting downloaded history to the last n commits. | Yes |
git fetch <remote> <branch> | Fetches updates only for a specific branch from a specific remote. | Yes |
FAQ (People Also Ask)
Does git fetch overwrite local changes?
No. git fetch never modifies your working directory files, your index (staging area), or your local branches. It only downloads new commits from the remote server into the internal .git/objects/ folder and updates the read-only pointers in .git/refs/remotes/. It is completely safe to run at any time, even if you have unstaged or uncommitted work.
What is the difference between git fetch and git pull?
git pull is a composite command. When you run git pull, Git executes git fetch behind the scenes to download the remote commits, and then immediately runs git merge (or git rebase if configured) to integrate those downloaded commits into your current local branch. git fetch is the first, safe step of that process.
How do I fetch a single branch from a remote?
If you are on a slow connection and only want to download changes for a specific branch, you can specify the remote and branch:
git fetch origin feature-login
This will download only the commits related to feature-login and update origin/feature-login inside .git/refs/remotes/origin/feature-login.
How can I see what was downloaded after a fetch?
You can compare your local branch to the remote branch using git log. For example, to see the commit messages of the fetched commits on main, run:
git log main..origin/main --oneline
To view the actual file diffs, use:
git diff main..origin/main
Test Your Knowledge
Loading quiz…