Home / Alt manpages / gitprotocol-pack(5)

  • gitprotocol-pack(5)
  • File format
  • linux

Trace a Git Fetch and Read Its Pack Protocol

You will capture a Git smart-transport exchange, identify reference discovery, and tell whether a request reached packfile negotiation. The workflow uses a disposable local repository and git ls-remote, so it does not change a real clone or push anything. Allow about fifteen minutes. You need Git 2.43.0 or a nearby release, a shell, and permission to create files under /tmp.

The installed reference on this machine is git-man 1:2.43.0-1ubuntu7.3, documenting Git 2.43.0. Packet details and trace wording can vary between Git releases, but the protocol stages described here are the behaviour documented by that local manual page.

1. Check the tracing controls

GIT_TRACE shows Git's process activity. GIT_TRACE_PACKET shows packet-line traffic exchanged by the transport helpers. Both are diagnostic controls for one command when placed before it; they do not become repository configuration.

$ git --version
git version 2.43.0
$ command -v git
/usr/bin/git

Checkpoint: do not paste a trace from a private remote into a public issue without reading it first. It can expose repository paths, host names, reference names and object IDs. It should not be treated as a secret-free log merely because it is text.

2. Prepare a disposable file transport

For a repeatable test, create a bare repository and one commit under /tmp. These commands change only the temporary directory. No elevated privileges are needed.

$ trace_root=$(mktemp -d /tmp/git-pack-trace-XXXXXX)
$ git -C "$trace_root" init --bare source.git
$ git -C "$trace_root" init work
$ git -C "$trace_root/work" config user.email [email protected]
$ git -C "$trace_root/work" config user.name Tester
$ printf 'hello\n' > "$trace_root/work/README"
$ git -C "$trace_root/work" add README
$ git -C "$trace_root/work" commit -m initial
$ git -C "$trace_root/work" push "$trace_root/source.git" HEAD:refs/heads/main

Use the path printed by mktemp in the next command. The bare repository is the server-side endpoint for this test; the file transport runs git-upload-pack locally and connects it to the client through a pipe.

3. Trace reference discovery without requesting a pack

ls-remote asks for the remote references and can finish after that discovery phase. Force protocol version 1 for a readable demonstration of the version advertisement and capability list:

$ GIT_TRACE=1 GIT_TRACE_PACKET=1 \
  git -c protocol.version=1 ls-remote "file://$trace_root/source.git" 2>&1
... run_command: ... 'git-upload-pack ...source.git'
... packet:  upload-pack> version 1
... packet:    ls-remote< ... refs/heads/main\0multi_ack ...
... packet:    ls-remote< 0000
... packet:    ls-remote> 0000
<OBJECT_ID>\trefs/heads/main

The exact timestamps, temporary path and object ID will differ. Look for version 1, a reference such as refs/heads/main, a NUL-separated capability list, and the 0000 flush packet. The manual describes HEAD as the first advertised ref when it is valid, followed by the other refs in C locale order. Here, the test repository has no valid symbolic HEAD pointing at main, so seeing only refs/heads/main is expected.

There is no packfile in this exchange. This is a useful boundary when troubleshooting: a failure before the flush packet points towards transport or reference discovery, while a fetch that continues past discovery enters negotiation.

4. Trace an actual fetch into a separate directory

Now clone the same endpoint, still with tracing enabled. The clone writes to a new directory, not to the source repository.

$ GIT_TRACE_PACKET=1 \
  git -c protocol.version=1 clone "file://$trace_root/source.git" "$trace_root/clone" 2>&1
... packet:    clone> want <OBJECT_ID> multi_ack ... side-band-64k ...
... packet:    clone> 0000
... packet:    clone> done
... packet:  upload-pack> NAK
... packet:  upload-pack> ...
Cloning into '.../clone'...

Do not copy the ellipses as literal output. The first want identifies an advertised object the client requires. A shallow clone may also send shallow and deepen lines. The client sends have lines when it already has candidate objects, allowing the server to construct a smaller pack. A new clone normally has no useful have lines.

After negotiation, the server sends the packfile. With side-band or side-band-64k, each packet has a band byte: band 1 carries pack data, band 2 progress, and band 3 errors. Packet tracing is not a packfile decoder, so use it to locate the stage and error, not to reconstruct the binary data.

5. Compare protocol versions deliberately

The manual names version as the recognised extra parameter, with values 1 and 2. The client setting is protocol.version. For a one-command comparison, run:

$ GIT_TRACE_PACKET=1 \
  git -c protocol.version=2 ls-remote "file://$trace_root/source.git" 2>&1
... packet:    ls-remote> command=ls-refs
... packet:    ls-remote< ...

Version 2 changes the exchange after the initial negotiation: the client selects a command such as ls-refs rather than relying on the version 1 advertisement shape. Exact capability lines depend on the server and Git release. If a server or transport does not accept the requested version, retry without the temporary -c protocol.version=2 override before changing any persistent configuration.

6. Diagnose the common boundaries

If ls-remote cannot start git-upload-pack, check the endpoint path and transport first. For SSH, the documented operation is equivalent to running git-upload-pack remotely; an ssh:// repository path is absolute, while the user@host:path form is relative to that user's home directory. A wrong slash or home-relative path can therefore fail before packet negotiation.

Do not use git:// for a writable service merely because it is convenient. The pack protocol itself provides no authentication. The manual describes the Git transport as unauthenticated and warns that an enabled receive-pack service would be writable by anyone who can reach port 9418. Prefer an authenticated transport and server-side access controls for pushes.

When investigating a real fetch, keep the trace command read-only: use ls-remote, a throwaway clone, or a fetch into a disposable repository. Do not add --upload-pack, change a remote URL, or enable a receive service as a first diagnostic step. Those choices alter execution or access boundaries and are not required to read the exchange.

7. Remove the test state

Once you have checked the trace, remove the disposable directory. This is destructive, but the directory was created solely for this test and contains no required data:

$ rm -rf -- "$trace_root"
$ test ! -e "$trace_root" && echo 'temporary repository removed'
temporary repository removed

If you need the trace later, save only a reviewed copy outside the temporary repository and redact paths, host names and reference names before sharing it. There is nothing to undo in your real Git configuration because -c protocol.version=1 and the tracing variables applied to individual commands.

Done means

  • You can identify reference discovery, negotiation and packfile transfer in GIT_TRACE_PACKET output.
  • You know that ls-remote can stop after discovery without receiving a packfile.
  • You can test protocol version 1 or 2 for one command without changing persistent configuration.
  • You have treated traces as potentially sensitive operational data.
  • The disposable repository has been removed, or you have deliberately retained it for another test.