Fetch Objects with git fetch-pack, No Ref Updates

git fetch-pack pulls objects from a remote and stores them locally, then stops, leaving your branches untouched. It is the plumbing underneath git fetch. You will fetch one remote branch into a disposable test repository, verify the object actually arrived, and see first-hand why nothing moves until you tell it to.

1. Check the command and pick a safe test area

Confirm which Git you are running, then read its local synopsis rather than trusting memory:

$ git --version
$ git fetch-pack -h

On the machine used for this guide, that prints git version 2.43.0. The installed synopsis takes a repository followed by optional remote refs, with options such as --all, --keep, --thin, --include-tag, --depth and --no-progress. Work in a throwaway clone: the command writes objects straight into the current repository's object database, so do not point it at anything you would miss.

Checkpoint: if the version here is not the one you meant to test, stop. Options drift between Git releases, so check that release's manual before copying an example.

2. Fetch one named remote ref

Run the command from the destination repository. The repository argument can be a local path, an SSH-style location, or any other URL Git accepts. A ref such as refs/heads/main is relative to the remote's own ref namespace, not yours:

$ cd /path/to/destination-repository
$ git fetch-pack /path/to/remote-repository.git refs/heads/main

A successful run prints the object name and the ref it received:

eda5d8f6c0c87f17de79eb376dc408602d50df63 refs/heads/main

Your object name will differ; capture it if a script needs to act on the result. The command negotiates against commits already reachable from your local refs/ hierarchy, so if the two repositories share no common ancestor, expect a bigger download than you might guess.

3. Verify the object, then check the boundary that trips people up

Use the object name from the output, or the same commit resolved in a trusted copy of the remote, to confirm it landed:

$ git cat-file -t OBJECT_NAME_FROM_THE_OUTPUT
commit

Replace OBJECT_NAME_FROM_THE_OUTPUT with the 40-character ID your run printed. The object now exists in your object database, which is not the same as a branch having moved:

$ git show-ref
$ git rev-parse --verify refs/heads/main

The second command can fail with fatal: Needed a single revision if you did not already have a local main. That is expected, not broken: fetch-pack receives objects and reports remote refs, full stop. It is not the ref-updating workflow you get from a normal fetch.

Checkpoint: if all you actually wanted was an updated branch, stop reaching for fetch-pack and run git fetch REMOTE REFSpec instead. Keep using this lower-level command only when you deliberately need it, and only update a ref afterwards once you have inspected the returned object against your repository's policy.

4. Fetch more than one ref when a single name is not enough

$ git fetch-pack --all /path/to/remote-repository.git
$ git fetch-pack /path/to/remote-repository.git refs/heads/main refs/tags/v1.2.3

Leave the ref list empty and, per the manual, the command updates from every head the remote has. Treat that as a broad transfer request, not as permission for Git to create or update every matching local ref on its own.

5. Use depth and storage options on purpose, not by accident

$ git fetch-pack --depth=20 /path/to/remote-repository.git refs/heads/main

6. Diagnose the common failures

Recovery: there is no general undo for received objects. Unreferenced objects eventually become eligible for Git's own housekeeping, but do not force a prune just to reverse a test: pruning can take other unreachable work with it. If you built the destination purely for this exercise, discard that disposable repository through your normal, separately checked cleanup procedure.

Done means