Home / Alt manpages / gitprotocol-common(5)

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

Read Git pkt-lines and Validate Refnames Safely

Miscount a pkt-line's four-byte header and your parser will read the wrong number of bytes for the rest of its life. This covers enough of Git's common wire format to inspect a trace without that mistake, and shows how to validate reference names with the installed command before they go into a script or protocol message. Allow about 15 minutes. The examples use Git 2.43.0, matching the installed gitprotocol-common(5) manpage and the local git binary.

This is a read-and-check workflow: it creates no branches, alters no repository, and needs no elevated privileges. Keep the distinction clear: the protocol document describes bytes exchanged between Git programs, while git check-ref-format checks a name you may later use as a ref.

1. Confirm the Git version and the task

Start by recording the version whose behaviour you are testing:

$ git --version
git version 2.43.0

The local manpage is labelled Git 2.43.0 and defines rules shared by several over-the-wire protocols and file formats. It is not a command that reads a network stream for you: use it as the format specification, then use tracing or a small parser when you actually need to inspect traffic.

Checkpoint

If your version differs, do not silently attach the local examples to another implementation. Check that version's documentation before relying on edge-case behaviour.

2. Validate a full refname before using it

A protocol refname is either HEAD or a name beginning with refs/. The normal hierarchy uses names such as refs/heads/main and refs/tags/v1.2.0. Validate the complete name, prefix included:

$ git check-ref-format refs/heads/release/2026-q3
$ printf 'exit status: %s\n' "$?"
exit status: 0

$ git check-ref-format 'refs/heads/release..candidate'
$ printf 'exit status: %s\n' "$?"
exit status: 1
  • Zero status: the name passes this syntax check.
  • Non-zero status: it must not be sent or created as that ref. The common rules reject control characters, spaces, ~, ^, :, ?, *, [, backslashes, consecutive dots, a trailing slash or dot, a trailing .lock, and @{. They also require at least one slash for a general refname.

Do not confuse this with checking whether the ref exists. A valid name can still be absent, point at a different object, or be rejected by server policy.

3. Check a branch shorthand separately

When the input is a branch name rather than a complete refname, use branch mode. It prints the normalised name on success:

$ git check-ref-format --branch 'release/2026-q3'
release/2026-q3

$ git check-ref-format --branch 'bad..name'
fatal: 'bad..name' is not a valid branch name

This is not the same as prepending refs/heads/ yourself. The branch rules add branch-specific restrictions, and the command may print a normalised result. Capture that output if the accepted spelling is what you intend to use:

$ branch_name='release/2026-q3'
$ checked_branch=$(git check-ref-format --branch "$branch_name") || {
>   printf 'invalid branch name: %s\n' "$branch_name" >&2
>   exit 1
> }
$ printf 'validated branch: %s\n' "$checked_branch"
validated branch: release/2026-q3

Warning

Quote the variable. An unquoted ref supplied by a user can be split by the shell or expanded as a pattern before Git ever sees it. Validation is not authorisation either: do not let an otherwise valid name drive an administrative operation without checking who requested it.

4. Read the four-byte pkt-line length

Many Git protocol payloads travel in pkt-lines. The first four bytes are hexadecimal and give the total byte length of the packet, including those four length bytes. The rest is the payload. That length is not the number of visible characters, and it is not merely the payload length either.

The small examples from the format, written as visible text:

0006a\n   total length 6, payload "a\n"
0005a      total length 5, payload "a"
000bfoobar\n
             total length 11, payload "foobar\n"
0004       total length 4, empty payload

The newline in 0006a\n is part of the payload, and so part of the six-byte total. A receiver must accept a non-binary line with or without its trailing LF, treating the payload the same either way once the LF is stripped. A pkt-line can also carry binary data, so a parser must handle bytes, not assume text encoding.

Checkpoint

For a packet beginning 000b, subtract four and read seven payload bytes. If your parser reads eleven, it has counted the header twice.

5. Keep empty and flush packets distinct

0004 is an empty data pkt-line. 0000 is a flush-pkt, a control marker that ends a section and needs different handling. Do not collapse both into the same empty string in a parser:

0004  data packet with zero payload
0000  flush-pkt, section boundary

The specification says implementations should not normally send 0004, but a receiver still has to tell it apart from a flush. A parser that treats every zero-length payload as end-of-section will stop early the moment it meets an empty data packet.

The maximum payload is 65,516 bytes, so the largest permitted total pkt-line length is 65,520 bytes, the hexadecimal length fff0. Reject a length above that before you allocate a buffer for it. That is both a protocol check and a resource-exhaustion boundary for a long-running service.

6. Investigate a real exchange without changing state

When a Git operation behaves unexpectedly, capture packet diagnostics around a read-only operation such as listing refs. The exact trace depends on the transport and negotiated protocol, so treat it as evidence rather than a fixed transcript:

$ GIT_TRACE_PACKET=1 git ls-remote https://git.example.invalid/TEAM/PROJECT.git
fatal: unable to access 'https://git.example.invalid/TEAM/PROJECT.git/': Could not resolve host: git.example.invalid

That address is deliberately a placeholder and will fail. Replace it only with a repository you are authorised to inspect, and do not paste credentials into the URL. Packet traces can expose repository names, capabilities and other request details, so store them with the same care as other operational logs.

git ls-remote reads advertised references and does not update your local refs. It still contacts a server, may invoke credentials, and may be disallowed by a network policy. If you need a fully offline test, validate names with git check-ref-format and feed known byte sequences to your own parser instead.

7. Recover from the common mistakes

  • Validation fails. Preserve the original input for an error message, but do not retry by stripping punctuation or swapping characters automatically; that can send an operation to a different ref entirely.
  • A trace looks truncated. Check whether your reader treated 0000 as ordinary data, or confused 0004 with a flush, then verify the four-byte length against the actual byte count.
  • A ref is syntactically valid but will not fetch or push. Inspect permissions, server policy and whether the ref actually exists. The common format rules grant no access on their own.
  • A diagnostic command unexpectedly starts an update. Stop and review the command before retrying. The examples here use validation and ref listing only; no elevated privileges are needed.

Done means

  • Recorded the installed Git version and checked version-specific documentation when it mattered.
  • Validated complete refs with git check-ref-format, and branch shorthands with --branch.
  • Counted the four-byte header as part of the pkt-line's total length.
  • Told an empty data packet apart from a flush-pkt: 0004 versus 0000.
  • Built a parser that accepts binary payloads, enforces the 65,520-byte total limit, and never allocates from an unchecked length.
  • Kept packet traces and repository access inside the authorisation and logging rules of the system you were diagnosing.