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.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Checked against: Git 2.43.0 and the installed
git-mandocumentation. - Time: about 10 minutes.
- You need: Git, a repository you can write to, and a remote repository with a branch you can read. Nothing here needs elevated privileges.
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
- Everything the remote exposes: add
--all. This can pull down considerably more data than a named ref, and it still only reports refs; it does not set up the usual local remote-tracking refs.
$ git fetch-pack --all /path/to/remote-repository.git
- A short, explicit list instead: name each ref you want.
$ 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
--depth=Nlimits fetched history to ancestor chains no longer thanNcommits. This creates or extends a shallow repository and can affect later operations that need older ancestry.--deepen-relativeextends an already-shallow repository by counting depth from the existing shallow boundary rather than from each remote tip. The installed 2.43.0 manual also documents--shallow-since,--shallow-excludeand--refetchfor more specific shallow or partial-clone needs.--keepstores the received data as one pack instead of runninggit unpack-objects. Pass it twice and the pack is locked against repacking, useful for a carefully managed transfer, but it changes pack maintenance and eats disk space.--thincuts network traffic by allowing deltas based on objects you already have locally. It depends on the receiver actually having those base objects.
$ git fetch-pack --depth=20 /path/to/remote-repository.git refs/heads/main
6. Diagnose the common failures
- Remote command not found. Over SSH, the remote service may not load the login shell's private
PATH. Use--upload-pack=/absolute/path/to/git-upload-packwhen the remote Git installation sits outside its service path, and confirm that path with the remote administrator first. - Unexpectedly large transfer. Narrow the request to named refs and check whether the destination shares a common ancestor with the remote.
--all, and an empty ref list, are both broad requests by design. - No visible branch change. Inspect the printed ref and object with
git cat-fileinstead. This is normal forfetch-pack; reach forgit fetchwhen you actually need remote-tracking refs and ordinary fetch behaviour. - Progress noise ruins scripted output. Add
--no-progress. Add--quietonly once you have another way to record failures and results.
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
- Version checked: you identified the installed Git version and read its local options before trusting an example.
- Ref fetched: you pulled a named ref into a repository you were safe to change.
- Object verified: you confirmed the object's presence with
git cat-file -t. - Boundary understood: you saw that receiving objects does not, by itself, move a local branch.
- Right tool chosen: you reached for
git fetchinstead whenever an ordinary ref update was the actual goal.