Home / Alt manpages / gitprotocol-pack(5)

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

Trace a Git Fetch and Read Its Pack Protocol

Trace a Git fetch and you can finally tell whether it died during reference discovery or deep in packfile negotiation. The workflow builds a disposable local repository and uses git ls-remote, so it never touches a real clone or pushes 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 releases, but the protocol stages described here match 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 a single command when placed before it; neither becomes repository configuration.

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

Warning

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. Text does not automatically mean secret-free.

2. Prepare a disposable file transport

For a repeatable test, build a bare repository and one commit under /tmp. These commands only touch that temporary directory, and need no elevated privileges:

$ 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 mktemp printed for 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 stop right 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

Timestamps, the temporary path and the object ID will differ for you. 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.

Checkpoint

There is no packfile in this exchange yet. A failure before the flush packet points to transport or reference discovery; a fetch that continues past discovery has entered negotiation.

4. Trace an actual fetch into a separate directory

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 names an advertised object the client requires; a shallow clone may also send shallow and deepen lines. The client sends have lines when it already holds candidate objects, so the server can build a smaller pack, but a fresh clone normally has no useful have lines to offer.

After negotiation the server sends the packfile. With side-band or side-band-64k, each packet carries a band byte: band 1 is pack data, band 2 is progress, band 3 is errors. Packet tracing is not a packfile decoder, so use it to locate the stage and the 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 picks a command such as ls-refs instead of relying on the version 1 advertisement shape. Exact capability lines depend on the server and Git release. If a server or transport will not accept the requested version, retry without the temporary -c protocol.version=2 override before you change 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 a home-relative path can fail before packet negotiation ever starts.

Warning

Do not use git:// for a writable service just because it is convenient. The pack protocol itself provides no authentication, and the manual describes the Git transport as unauthenticated: 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. None of those are needed just to read the exchange, and all of them alter execution or access boundaries.

7. Remove the test state

Once you have checked the trace, remove the disposable directory. This is destructive, but the directory exists only for this test and holds nothing you need:

$ 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: -c protocol.version=1 and the tracing variables applied to individual commands only.

Done means

  • Can identify reference discovery, negotiation and packfile transfer in GIT_TRACE_PACKET output.
  • Know ls-remote can stop after discovery without ever receiving a packfile.
  • Can test protocol version 1 or 2 for one command without touching persistent configuration.
  • Treated traces as potentially sensitive operational data.
  • Removed the disposable repository, or deliberately kept it for another test.