Skip to main content

Overview

Git uses sophisticated protocols to efficiently transfer packfile data between repositories. These protocols minimize data transfer by negotiating what objects the client already has and sending only the necessary differences.
There are two protocol sets: one for fetching data (upload-pack/fetch-pack) and one for pushing (receive-pack/send-pack).

Supported Transports

Git supports four primary transports:

SSH

Secure, authenticated transport using SSH remote execution

Git Protocol

Fast, unauthenticated daemon protocol on port 9418

HTTP/HTTPS

Widely compatible, firewall-friendly transport (see gitprotocol-http)

File

Local filesystem access using pipes

pkt-line Format

All Git protocols use the pkt-line format for framing:
pkt-line = 4-byte-hex-length + data

Examples:
  "0006a\n"     → length 6, data "a\n"
  "0000"        → flush packet (signals end of section)
  "0009done\n"  → length 9, data "done\n"

Special Packets

  • Flush packet: 0000 - Marks end of a data section
  • Error packet: PKT-LINE("ERR " + message) - Terminates the connection
  • Delimiter packet: 0001 - Separates sections (protocol v2)
def write_pkt_line(data):
    if data is None:
        return b'0000'  # flush packet
    length = len(data) + 4
    return f'{length:04x}'.encode() + data

def read_pkt_line(stream):
    length_hex = stream.read(4)
    if length_hex == b'0000':
        return None  # flush packet
    length = int(length_hex, 16)
    data = stream.read(length - 4)
    return data

Transport Initiation

Git Transport

The Git daemon expects:
git-proto-request = command SP pathname NUL
                    [ host-parameter NUL ]
                    [ NUL extra-parameters ]

Example:
  0033git-upload-pack /project.git\0host=myserver.com\0
With extra parameters (e.g., protocol version):
003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0

SSH Transport

SSH transport invokes commands directly:
# Absolute path (ssh:// URLs)
ssh user@example.com "git-upload-pack '/project.git'"

# Relative to home (scp-style URLs)
ssh user@example.com "git-upload-pack 'project.git'"

# Home directory expansion
ssh user@example.com "git-upload-pack '~alice/project.git'"
Extra parameters can be sent via the GIT_PROTOCOL environment variable if ssh.variant supports it.

File Transport

Runs upload-pack or receive-pack locally via pipe:
git-upload-pack /path/to/repo.git

Fetching Data: Reference Discovery

The server immediately responds with available references:
$ echo -e -n "0045git-upload-pack /repo.git\0host=example.com\0\0version=1\0" | \
  nc -v example.com 9418

000eversion 1
00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack
  side-band side-band-64k ofs-delta shallow no-progress include-tag
00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration
003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master
003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9
003c525128480b96c89e6418b1e40909bf6c5b2d580f refs/tags/v1.0
003fe92df48743b7bc7d26bcaabfddde0a1e20cae47c refs/tags/v1.0^{}
0000

Advertised Refs Format

advertised-refs = *1("version 1")
                  (no-refs / list-of-refs)
                  *shallow
                  flush-pkt

first-ref = PKT-LINE(obj-id SP refname NUL capability-list)
other-ref = PKT-LINE(obj-id SP refname ["^{}"])
  • Refs MUST be sorted by name (C locale)
  • HEAD appears first if valid
  • Capabilities appear after NUL on first ref
  • Peeled tags (^) immediately follow their tag

Common Capabilities

Enables richer ACK responses during negotiation to find common commits faster.
Allows sending packfiles that reference objects not included (assumes client has them).
Multiplexes packfile data with progress and error messages on separate channels.
Permits offset-based delta encoding in packfiles for better compression.
Supports shallow clones with limited history depth.

Fetching Data: Packfile Negotiation

The client tells the server what it wants and what it has:

Upload Request

upload-request = want-list
                 *shallow-line
                 *1depth-request
                 [filter-request]
                 flush-pkt

first-want = PKT-LINE("want" SP obj-id SP capability-list)
additional-want = PKT-LINE("want" SP obj-id)

shallow-line = PKT-LINE("shallow" SP obj-id)
depth-request = PKT-LINE("deepen" SP depth) /
                PKT-LINE("deepen-since" SP timestamp) /
                PKT-LINE("deepen-not" SP ref)
Example:
C: 0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack \
     side-band-64k ofs-delta\n
C: 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe\n
C: 0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a\n
C: 0000

Have Negotiation

Client sends OIDs it already has:
C: 0032have 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
C: 0032have 74730d410fcb6603ace96f1dc55ea6196122532d\n
C: [... up to 32 haves, then flush ...]
C: 0000

S: 003aACK 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 continue\n
S: 003aACK 74730d410fcb6603ace96f1dc55ea6196122532d continue\n
S: 0008NAK\n
In multi_ack mode, the client sends blocks of 32 haves, allowing continuous negotiation without waiting for all haves to be sent.

Negotiation Strategies

multi_ack mode:
  • Server ACKs common commits with “ACK obj-id continue”
  • After finding acceptable base, blindly ACKs all haves
  • Sends NAK, waits for “done” or more haves
multi_ack_detailed mode:
  • “ACK obj-id common” - Identifies common commits
  • “ACK obj-id ready” - Signals readiness to send packfile
No multi_ack:
  • Server sends single “ACK obj-id” for first common object
  • Remains silent until client sends “done”

Termination

C: 0009done\n

S: 0031ACK 74730d410fcb6603ace96f1dc55ea6196122532d\n
S: [PACKFILE]
The canonical client gives up after 256 unacknowledged haves, assuming no common history. However, this only activates if at least one “ACK continue” was received previously.

Packfile Data Transfer

After negotiation, the server streams the packfile:
packfile = "PACK" + version + object-count + objects + checksum
See gitformat-pack for detailed packfile structure.

Side-band Multiplexing

If side-band-64k was negotiated:
Each packet:
  4-byte length
  1-byte channel:
    1 = packfile data
    2 = progress messages (→ stderr)
    3 = error messages
  N-byte data (up to 65519 bytes)
def read_sideband(stream):
    while True:
        pkt = read_pkt_line(stream)
        if pkt is None:
            break
        
        channel = pkt[0]
        data = pkt[1:]
        
        if channel == 1:
            # Packfile data
            process_packfile_data(data)
        elif channel == 2:
            # Progress
            sys.stderr.write(data.decode())
        elif channel == 3:
            # Error
            raise Exception(f"Server error: {data.decode()}")

Pushing Data: Reference Discovery

Similar to fetching, but different capabilities:
S: 006274730d410fcb6603ace96f1dc55ea6196122532d refs/heads/local\0
     report-status delete-refs ofs-delta\n
S: 003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug\n
S: 003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master\n
S: 0000

Push Capabilities

  • report-status / report-status-v2 - Detailed update status
  • delete-refs - Allow reference deletion
  • ofs-delta - Use offset deltas in packfile
  • atomic - All-or-nothing updates
  • push-options - Send additional metadata

Pushing Data: Update Request

update-requests = *shallow ( command-list | push-cert )

command-list = PKT-LINE(command NUL capability-list)
               *PKT-LINE(command)
               flush-pkt

command = create / delete / update

create = zero-id SP new-id SP name
delete = old-id SP zero-id SP name  
update = old-id SP new-id SP name
Example:
C: 00677d1665144a3a975c05f1f43902ddaf084e784dbe \
        74730d410fcb6603ace96f1dc55ea6196122532d \
        refs/heads/debug\n
C: 006874730d410fcb6603ace96f1dc55ea6196122532d \
        5a3f6be755bbb7deae50065988cbfa1ffa9ab68a \
        refs/heads/master\n
C: 0000
C: [PACKFILE]
  • Packfile MUST NOT be sent for delete-only operations
  • Packfile MUST be sent for create/update (even if empty)
  • Server validates that old-id matches current ref value

Push Certificates

For signed pushes:
push-cert = PKT-LINE("push-cert" NUL capability-list LF)
            PKT-LINE("certificate version 0.1" LF)
            PKT-LINE("pusher" SP ident LF)
            PKT-LINE("pushee" SP url LF)
            PKT-LINE("nonce" SP nonce LF)
            *PKT-LINE("push-option" SP option LF)
            PKT-LINE(LF)
            *PKT-LINE(command LF)
            *PKT-LINE(gpg-signature-lines LF)
            PKT-LINE("push-cert-end" LF)

Report Status

After receiving the packfile:
report-status = unpack-status
                1*(command-status)
                flush-pkt

unpack-status = PKT-LINE("unpack" SP ("ok" / error-msg))
command-status = PKT-LINE(("ok" / "ng") SP refname [SP error-msg])
Example:
S: 000eunpack ok\n
S: 0018ok refs/heads/debug\n
S: 002ang refs/heads/master non-fast-forward\n
Extended format includes option lines for proc-receive hook:
S: 0018ok refs/heads/feature\n
S: 001foption refname refs/pull/123\n
S: 0025option old-oid <40-hex>\n
S: 0025option new-oid <40-hex>\n

Shallow Clones

Request limited history depth:
C: 000ddeepen 1\n
C: 0000

S: 0035shallow 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01\n
S: 0000

Shallow Update

Server responds with shallow/unshallow lines:
shallow-update = *shallow-line
                 *unshallow-line  
                 flush-pkt

shallow-line = PKT-LINE("shallow" SP obj-id)
unshallow-line = PKT-LINE("unshallow" SP obj-id)

Partial Clone

Request filtered packfile:
C: 002bfilter blob:limit=1m\n
The server omits objects not matching the filter (see git rev-list --filter).
The resulting pack must be marked as a .promisor pack, and missing objects can be fetched on-demand later.

Error Handling

ERR-line = PKT-LINE("ERR" SP explanation-text)
Error packets immediately terminate the connection:
def handle_packet(pkt):
    if pkt.startswith(b'ERR '):
        raise ProtocolError(pkt[4:].decode())

Protocol Versions

Version 1

Original protocol with stateless HTTP support. Described in this document.

Version 2

Modern protocol with server-side filtering, better capability negotiation. See gitprotocol-v2.

Performance Optimization

1

Enable side-band-64k

Larger packets reduce overhead for high-bandwidth connections.
2

Use ofs-delta

Offset-based deltas compress better than reference deltas.
3

multi_ack_detailed

Reduces round trips during have/want negotiation.
4

Protocol v2

Eliminates unnecessary ref advertisement in many cases.

Security Considerations

  • Git daemon (port 9418) has NO authentication - use only for public repositories
  • SSH provides authentication and encryption
  • HTTPS provides encryption and optional authentication
  • receive-pack over Git protocol makes repositories world-writable