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:

  1. Downloading the history (network communication via git fetch).
  2. Integrating the history (local merges or rebases via git merge or git 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 origin and expects to see their files in VS Code update. When they don’t, they assume the command failed.
  • The Cause: git fetch does 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:
    git merge origin/main
    Or run git pull if 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-branch is 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 -r and 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 --prune to clean up the stale remote-tracking references.

Command Quick Reference

CommandActionNetwork Activity
git fetchFetches updates from the default remote (origin).Yes
git fetch <remote>Fetches updates from a specific named remote.Yes
git fetch --allFetches updates from all configured remotes.Yes
git fetch --pruneFetches and deletes local references to remote branches that were deleted on the server.Yes
git fetch --dry-runShows 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…