Git Transfer Protocols
This blog post is an extended write-up of a short talk I gave at a local meetup.
Git status messages are sent from the server#
Have you ever noticed these status messages while cloning a git repository?
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/
Cloning into 'linux'...
remote: Enumerating objects: 11687137, done.
remote: Counting objects: 100% (273/273), done.
remote: Compressing objects: 100% (149/149), done.
Receiving objects: 0% (11709/11687137), 6.32 MiB | 1.74 MiB/s
...
Well, turns out they are sent verbatim by the git server:

Now you may ask: Can we – with a custom git server – inject our own messages into the unchanged git client?
And the answer is: Yes, we can! This includes sending arbitrary ANSI escape codes (colors, cursor positions, etc.) or simply sending an infinite stream of sideband messages without ever progressing to the actual data, thereby stalling the clone. Which makes this behavior possible:
Note:
- This sends a dynamically/randomly generated snake game,
- followed by text of the source code of the tool itself to stdout.
- The
git cloneoperation is actually successful and the final repository on-disk contains the tool’s source code.
To be clear: the sideband is a documented part of the protocol, nothing here is new. I just found this behavior of verbatim printing to be unexpected and wanted to write about it.
Git transfer protocols#
Once, while cloning a git repository, I wondered what happens underneath.
This blog post dives deeper into how git actually transfers data during repository operations like git clone/fetch/pull/push.
From personal experience there are lots of excellent resources about git-internals with respect to how git stores data at rest / the on-disk repository format. However, I found much less material online regarding the transfer protocols.
Resources#
The git source code itself contains extensive documentation about the various transfer protocols. Directly relevant are for example these files:
- Documentation/gitprotocol-v2.adoc
- Documentation/gitprotocol-pack.adoc
- Documentation/gitprotocol-common.adoc
- Documentation/gitprotocol-http.adoc
These documents are also available as manual pages with the same name; e.g. man gitprotocol-v2.
The Git book also contains chapters about the protocols: Git on the Server - The Protocols and Git Internals - Transfer Protocols.
These will form the base sources of the following sections.
One Wire Protocol, several transports#
One key thing to realize is that there is a single underlying protocol – the Git Wire Protocol (version 2).
This is then wrapped in slightly varying ways with additional metadata into various transport protocols: file://, git://, ssh://, http/s://.
First, an overview of the different transports (before we dive deeper into the underlying common wire protocol):
| transport | encrypted | authenticated | write access | port |
|---|---|---|---|---|
file:// | n/a | filesystem perms | yes | n/a |
git:// | no | no | off by default | 9418 |
ssh:// | yes | yes | yes | 22 |
http:// | no | optional | smart only | 80 |
https:// | yes (TLS) | optional | smart only | 443 |
file://#
This protocol is used when cloning locally, e.g.:
mkdir -p /tmp/a ; cd /tmp/a ; git init
cd /tmp/ ; git clone a b
In the background, git doesn’t stay in a single process – which one could expect – but instead executes another child process: git-upload-pack.
Here is the corresponding output of execsnoop from bcc (the BPF Compiler Collection; Debian ships it as bpfcc-tools), a small tool that prints one line for every process that gets executed on the system:
# execsnoop-bpfcc
...
git 2787271 2615972 0 /usr/bin/git clone a b
sh 2787277 2787271 0 /bin/sh -c git-upload-pack '/tmp/a/.git' git-upload-pack '/tmp/a/.git'
git-upload-pack 2787279 2787277 0 /usr/lib/git-core/git-upload-pack /tmp/a/.git
...
This already hints at the distributed nature of git and the git terminology summarized later regarding clone/fetch/pull and push from the client and server perspectives.
Interestingly, the execution of git-upload-pack goes via an intermediate shell instance /bin/sh.
Parent (client role) and child process (server role) both communicate (most of) the git wire protocol over pipes.
To get a first glimpse at the structure of the sent messages, I observed the reads and writes of both processes with strace:
(-f follow forks, -o specifies output file, -qq silent/quiet, -e … dump reads from fd0 and writes to fd1 in hex + ascii, -s specify length of strings/buffer contents to dump)
$ strace -f -o /tmp/strace.log -qq -e read=0 -e write=1 -s 10000 git clone a b
Cloning into 'b'...
warning: You appear to have cloned an empty repository.
done.
$ cat /tmp/strace.log | grep 'execve('
3598361 execve("/usr/bin/git", ["git", "clone", "a", "b"], 0x7ffc44b6c648 /* 47 vars */) = 0
3598369 execve("/bin/sh", ["/bin/sh", "-c", "git-upload-pack '/tmp/a/.git'", "git-upload-pack '/tmp/a/.git'"], 0x55c72f30be40 /* 49 vars */ <unfinished ...>
3598374 execve("/usr/lib/git-core/git-upload-pack", ["git-upload-pack", "/tmp/a/.git"], 0x5637e0a54da8 /* 49 vars */ <unfinished ...>
Here are reads (from stdin (file descriptor 0)) and writes (to stdout (file descriptor 1)) of the child process:
$ cat /tmp/strace.log | grep -B1 '^ |' | grep -v -- --
3598374 <... write resumed>) = 14
| 00000 30 30 30 65 76 65 72 73 69 6f 6e 20 32 0a 000eversion 2. |
3598374 <... write resumed>) = 4
| 00000 30 30 31 35 0015 |
3598374 <... write resumed>) = 17
| 00000 61 67 65 6e 74 3d 67 69 74 2f 32 2e 34 37 2e 33 agent=git/2.47.3 |
| 00010 0a . |
3598374 <... write resumed>) = 4
| 00000 30 30 31 33 0013 |
3598374 <... write resumed>) = 15
| 00000 6c 73 2d 72 65 66 73 3d 75 6e 62 6f 72 6e 0a ls-refs=unborn. |
3598374 <... write resumed>) = 4
| 00000 30 30 32 30 0020 |
3598374 <... write resumed>) = 28
| 00000 66 65 74 63 68 3d 73 68 61 6c 6c 6f 77 20 77 61 fetch=shallow wa |
| 00010 69 74 2d 66 6f 72 2d 64 6f 6e 65 0a it-for-done. |
3598374 <... write resumed>) = 4
| 00000 30 30 31 32 0012 |
3598374 <... write resumed>) = 14
| 00000 73 65 72 76 65 72 2d 6f 70 74 69 6f 6e 0a server-option. |
3598374 <... write resumed>) = 4
| 00000 30 30 31 37 0017 |
3598374 <... write resumed>) = 19
| 00000 6f 62 6a 65 63 74 2d 66 6f 72 6d 61 74 3d 73 68 object-format=sh |
| 00010 61 31 0a a1. |
3598374 <... write resumed>) = 4
| 00000 30 30 30 30 0000 |
3598374 <... read resumed>"0014", 4) = 4
| 00000 30 30 31 34 0014 |
3598374 <... read resumed>"command=ls-refs\n", 16) = 16
| 00000 63 6f 6d 6d 61 6e 64 3d 6c 73 2d 72 65 66 73 0a command=ls-refs. |
3598374 <... read resumed>"0014", 4) = 4
| 00000 30 30 31 34 0014 |
3598374 <... read resumed>"agent=git/2.47.3", 16) = 16
| 00000 61 67 65 6e 74 3d 67 69 74 2f 32 2e 34 37 2e 33 agent=git/2.47.3 |
3598374 <... read resumed>"0016", 4) = 4
| 00000 30 30 31 36 0016 |
3598374 <... read resumed>"object-format=sha1", 18) = 18
| 00000 6f 62 6a 65 63 74 2d 66 6f 72 6d 61 74 3d 73 68 object-format=sh |
| 00010 61 31 a1 |
3598374 <... read resumed>"0001", 4) = 4
| 00000 30 30 30 31 0001 |
3598374 <... read resumed>"0009", 4) = 4
| 00000 30 30 30 39 0009 |
3598374 <... read resumed>"peel\n", 5) = 5
| 00000 70 65 65 6c 0a peel. |
3598374 read(0, "000c", 4) = 4
| 00000 30 30 30 63 000c |
3598374 read(0, "symrefs\n", 8) = 8
| 00000 73 79 6d 72 65 66 73 0a symrefs. |
3598374 read(0, "000b", 4) = 4
| 00000 30 30 30 62 000b |
3598374 read(0, "unborn\n", 7) = 7
| 00000 75 6e 62 6f 72 6e 0a unborn. |
3598374 read(0, "0014", 4) = 4
| 00000 30 30 31 34 0014 |
3598374 read(0, "ref-prefix HEAD\n", 16) = 16
| 00000 72 65 66 2d 70 72 65 66 69 78 20 48 45 41 44 0a ref-prefix HEAD. |
3598374 read(0, "001b", 4) = 4
| 00000 30 30 31 62 001b |
3598374 read(0, "ref-prefix refs/heads/\n", 23) = 23
| 00000 72 65 66 2d 70 72 65 66 69 78 20 72 65 66 73 2f ref-prefix refs/ |
| 00010 68 65 61 64 73 2f 0a heads/. |
3598374 read(0, "001a", 4) = 4
| 00000 30 30 31 61 001a |
3598374 read(0, "ref-prefix refs/tags/\n", 22) = 22
| 00000 72 65 66 2d 70 72 65 66 69 78 20 72 65 66 73 2f ref-prefix refs/ |
| 00010 74 61 67 73 2f 0a tags/. |
3598374 read(0, "0000", 4) = 4
| 00000 30 30 30 30 0000 |
3598374 <... write resumed>) = 50
| 00000 30 30 32 65 75 6e 62 6f 72 6e 20 48 45 41 44 20 002eunborn HEAD |
| 00010 73 79 6d 72 65 66 2d 74 61 72 67 65 74 3a 72 65 symref-target:re |
| 00020 66 73 2f 68 65 61 64 73 2f 6d 61 69 6e 0a 30 30 fs/heads/main.00 |
| 00030 30 30 00 |
3598374 <... read resumed>"0000", 4) = 4
| 00000 30 30 30 30 0000 |
git://#
This is the Git-native protocol running on TCP port 9418. Since it offers no authentication and no encryption, it is commonly used for anonymous, read-only access.
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
ssh://#
This transport uses SSH, and therefore offers encryption and authentication; and allows for both read and write access. It is commonly used with git hosting services.
http:// and https://#
These transports support encryption via TLS (HTTPS), and optionally authentication (e.g. via HTTP basic auth with username and password). They are commonly used with git hosting services.
There are actually two different variations of these transports:
The dumb protocol allows serving a git repository read-only as a static website. The smart protocol allows serving a git repository with additional dynamic features requiring a dedicated server, e.g. write-access, or shallow clones (clones up to a specific commit history depth).
Exploring the Git Wire Protocol V2 in practice#
Now we will analyze the git wire protocol v2 a bit more in detail, based on the (smart) HTTPS protocol. While setting up a git HTTPS server is not as trivial as for the other protocols, this transport is commonly used in the real world, and easy to inspect.
There are different ways we can inspect the network traffic:
As a first approximation, we can use git’s built-in debug options, e.g. with:
GIT_TRACE2=true GIT_TRACE_PACKET=true \
git clone https://github.com/octocat/Hello-World
Which produces the following output:
... pkt-line.c:85 packet: git< # service=git-upload-pack
... pkt-line.c:85 packet: git< 0000
... pkt-line.c:85 packet: git< version 2
... pkt-line.c:85 packet: git< agent=git/github-cda1d7094a30-Linux
... pkt-line.c:85 packet: git< ls-refs=unborn
... pkt-line.c:85 packet: git< fetch=shallow wait-for-done filter
... pkt-line.c:85 packet: git< server-option
... pkt-line.c:85 packet: git< object-format=sha1
... pkt-line.c:85 packet: git< 0000
... pkt-line.c:85 packet: clone< version 2
... pkt-line.c:85 packet: clone< agent=git/github-cda1d7094a30-Linux
... pkt-line.c:85 packet: clone< ls-refs=unborn
... pkt-line.c:85 packet: clone< fetch=shallow wait-for-done filter
... pkt-line.c:85 packet: clone< server-option
... pkt-line.c:85 packet: clone< object-format=sha1
... pkt-line.c:85 packet: clone< 0000
... pkt-line.c:85 packet: clone> command=ls-refs
... pkt-line.c:85 packet: clone> agent=git/2.55.0-Linux
... pkt-line.c:85 packet: clone> object-format=sha1
... pkt-line.c:85 packet: clone> 0001
... pkt-line.c:85 packet: clone> peel
... pkt-line.c:85 packet: clone> symrefs
... pkt-line.c:85 packet: clone> unborn
...
But to fully understand the context and exact bytes sent, and to not have to rely on git-internal self-reporting, a better approach is to externally capture the network traffic exactly as it happens with something like tcpdump or Wireshark.
This is possible, even for the encrypted HTTPS transport, since git uses libcurl:
- “libcurl” library is used for fetching and pushing repositories over http:// or https://, as well as by git-imap-send. If you do not need that functionality, use NO_CURL to build without it.
Curl supports writing out TLS Master Secrets needed for decryption in Wireshark by setting the environment variable SSLKEYLOGFILE:
wireshark -k -i any & disown
SSLKEYLOGFILE=/tmp/sslkeylogfile \
git clone https://github.com/octocat/Hello-World
In Wireshark, the TLS streams in the pcap traffic capture can then be decrypted by setting the appropriate options: Edit > Preferences > Protocols > TLS > (Pre)-Master-Secret log filename.
For the further analysis, instead of relying on external git repositories, we set up our own local HTTPS server with a deterministic repository-state to clone. This also gives easier server-side introspection and easier analysis of writes / pushes.
The code and the later referenced pcap files are available in this repository.
The Dockerfile can be built/run with the following commands and gates write access behind basic authentication with the credentials test:test:
podman build -t git-http-server .
podman run --rm -p 127.0.0.1:8443:443 -d --name git-http-server localhost/git-http-server
Overview of main commands during git operations#
Git uses the following “command” terminology for different operations from different perspectives/roles (client/server):
| client | server | |
|---|---|---|
| push | send-pack | receive-pack |
| clone/pull/fetch | fetch-pack | upload-pack |
For example, a clone is a conversation between the client’s fetch-pack and the server’s upload-pack.
Depending on the transport, these are launched as separate independent operating-system processes (which also have their own manual pages man git-send-pack, man git-receive-pack, man git-fetch-pack, man git-upload-pack), or other times they are integrated indirectly into git invocations.
Packet-Line Framing#
Almost everything in the git wire protocol is sent as a so called packet-line (or pkt-line). It is a basic length-value encoding scheme: each packet-line consists of:
- a 4-byte ASCII-hexadecimal length of the whole pkt-line (including this header itself),
- followed by variable length data that carries the actually important information.
As an example, when the client queries the server for a listing of remote references (ls-refs), the hexdump of the packet-line looks as follows:
00000000 30 30 31 34 63 6f 6d 6d 61 6e 64 3d 6c 73 2d 72 |0014command=ls-r|
00000010 65 66 73 0a |efs.|
The 0014 = 0x14 = 20 is the length of the whole packet-line.
The actual payload is command=ls-refs\n.
Regarding the trailing line feed, gitprotocol-pack(5) states, that
the sender SHOULD include a LF, but the receiver MUST NOT complain if it is not present.
Since sometimes the LF is omitted, the referenced code-repository contains a small helper-script show-pktlines.py that displays each pkt-line visually distinct on its own line.
It takes as input the Wireshark decrypted HTTPS traffic (“follow HTTP stream”) as a long hex string and outputs the respective pkt-lines contained therein.
The output of this script is used as basis for the further code listings of pkt-lines.
As a direct consequence of the length-encoding there are some special pkt-lines.
The empty pkt-line (hex 0004) SHOULD NOT be used according to the documentation.
Other special pkt-lines are those with a length smaller than the normal possible minimum length:
0000flush-pkt is used as end-of-message0001delim-pkt separates sections of a message0002response-end-pkt designates the end of a response
Read access: Git clone#
Here we use the following command as basis for inspecting read access via git clone:
SSLKEYLOGFILE=/tmp/sslkeylogfile GIT_TRACE_PACKET=true \
git -c http.sslVerify=false \
clone https://test:test@localhost:8443/git/test.git
Based on this pcap file we will walk in detail through a typical clone operation. The whole exchange, at a glance:
client > server: At first, we see the client’s initial request to theupload-packcommand/service:GET /git/test.git/info/refs?service=git-upload-pack HTTP/1.1 ...server > client: This is then followed by the server capability advertisement:000e b"version 2\n" 0015 b"agent=git/2.47.3\n" 0013 b"ls-refs=unborn\n" 0020 b"fetch=shallow wait-for-done\n" 0012 b"server-option\n" 0017 b"object-format=sha1\n" 0000These pkt-lines have the following meaning:
ls-refs unborn: The server supportsunbornreferences. This allows the client to later specify theunbornoption when requesting the commandls-refs. An unborn reference is a branch that does not exist yet, e.g.main(ormaster) on a freshly initialized, empty repository.fetch shallow: This allows the client to later specify a shallow clone, i.e. not the full history but only commits up to a certain depth/data/revision, to keep the local repository copy smaller.fetch wait-for-done: Later, when the pack-file is sent, the server keeps the connection open until the client sends an explicitdone, instead of ending the negotiation on its own. I think this allows for more dynamic data flows.server-option: This allows the client to later specify arbitrary key=value server options – an extension point for custom behavior in custom git hooks.object-format sha1: Specifies SHA1 as hash format for the content-addressed object storage. Historically git supported only SHA1. At the latest since SHAttered – a SHA1 hash collision – migration efforts to more modern cryptographically secure hash functions are ongoing and git now also supports SHA-256.
client > server: After this initial handshake, now the client sends the actual first command request: ls-refs; the server should list the references it knows about.POST /git/test.git/git-upload-pack HTTP/1.1 ... 0014 b"command=ls-refs\n" 0014 b"agent=git/2.47.3" 0016 b"object-format=sha1" 0001 0009 b"peel\n" 000c b"symrefs\n" 000b b"unborn\n" 0014 b"ref-prefix HEAD\n" 001b b"ref-prefix refs/heads/\n" 001a b"ref-prefix refs/tags/\n" 0000These pkt-lines have the following meaning:
command=ls-refs: Request reference advertisement from the server.peel: For annotated tags, the server should show the base non-tag object that the tag refers to. Since an annotated tag is itself an object, tags can point at other tags; peeling resolves that chain in one go. If tagapoints to tagbwhich points to the commitabcdef, the server additionally reports directly thataresolves toabcdef.symrefs: For references, the server should show the underlying object that is referenced.unborn: See description above.ref-prefix: This is a filter for references; the client cares about the referencesHEAD(current commit/branch),refs/heads/*(server-local branches), andrefs/tags/*(tags). This excludes e.g. branches that are remote to the server (e.g. if the server itself has other git remotes configured), or various other reference-types used by other git features.
server > client: The server answers with the requested references.0052 b"62cb84d4b62cc9db3329a94ffc3a4800640d039f HEAD symref-target:refs/heads/master\n" 003f b"62cb84d4b62cc9db3329a94ffc3a4800640d039f refs/heads/master\n" 0000Because of the previously specified
symrefsoption, it includes a second pkt-line resolving the indirectionHEAD>refs/heads/master>62cb84explicitly.client > server: Now the client has enough information to request the actual data it wants (the commit of the current branch).POST /git/test.git/git-upload-pack HTTP/1.1 ... 0011 b"command=fetch" 0014 b"agent=git/2.47.3" 0016 b"object-format=sha1" 0001 000d b"thin-pack" 000d b"ofs-delta" 0032 b"want 62cb84d4b62cc9db3329a94ffc3a4800640d039f\n" 0032 b"want 62cb84d4b62cc9db3329a94ffc3a4800640d039f\n" 0009 b"done\n" 0000These pkt-lines have the following meaning:
thin-pack: Client requests that the server send a thin pack: one with deltas which reference base objects not contained within the pack (but known to exist on the receiving end).A full description of pack-files is out-of-scope here. In short: pack-files are the main modern on-disk repository object-storage format and also used for transferring data between repositories. They store objects not as distinct individual files, but can store multiple objects in one single file. Besides the base-objects Commit, Tree, Blob, and (annotated) Tag, pack-files also support delta encoding against previously known (by the client) objects for more efficient compression.
With this option the client asks the server for such deltas, which reference base objects that the client already has. During a fresh clone, this is not relevant. But assume a client pulls updates to an existing repo from a server (on the protocol level it would send
havepkt-lines in addition to thewantpkt-lines); then this option allows delta-encoding against objects the client already has, without retransmitting them.ofs-delta: Client supports the modern PACKv2 format with delta by offset instead of oid (hash).The previously mentioned delta encoding in pack-files can reference other objects by two mechanisms: Either oid (objectid, =hash), or by relative-offset into the current pack-file. The first method is the older one and aligns with the content-addressable nature of git. The second method was introduced later. This option allows the server to use both methods.
want: Objects to retrieve; identified by their hash (object id).It was surprising to me that the same hash appears twice here; I suppose the client simply doesn’t de-duplicate hashes referenced by multiple names.
done: Indicates to the server that negotiation is finished and that the server should now send the actual repository data (pack-file).
server > client: Now the server sends the actual data, interleaved/multiplexed with status information:000d b"packfile\n" 0023 b"\x02Enumerating objects: 3, done.\n" 0022 b"\x02Counting objects: 33% (1/3)\r" 0022 b"\x02Counting objects: 66% (2/3)\r" 0046 b"\x02Counting objects: 100% (3/3)\rCounting objects: 100% (3/3), done.\n" 00c5 b"\x01PACK\x00\x00\x00\x02\x00\x00\x00\x03\x93\x07x\x9cu\xc9A\n\x80 \x10" b"\x05\xd0\xbd\xa7\x98}\x1bK\xd3\x84h\xd7AF\xe7GAR\xc4\x04\x1d\xbf.\xd0" ... 0043 b"\x02Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)\n" 0006 b"\x01\xa0" 0000These pkt-lines have the following meaning:
First, there is a fixed header pkt-line
packfileThen follow the multiplexed data streams, identified by their id 1-3:
- Stream 1: actual pack data
- Stream 2: progress messages
- Stream 3: error messages
Stream 2 is used in the initial demo to inject arbitrary messages from the server to the client. The messages are printed verbatim including control characters; it is possible to send many sideband progress messages, without continuing sending actual pack data.
As we can see, the protocol offers many parameters. It should also be noted that this is only one specific workflow; since later protocol steps depend on earlier specified parameters, there are many possibilities.
Write access: Git push#
In the same vein we can also record a push operation. Based on this pcap file we will walk in detail through a typical push operation. The whole exchange, at a glance:
For this, we first have to introduce some changes to our local repository. Here I chose to make these changes bit-for-bit reproducible by fixing all metadata fields in commits (e.g. timestamps), so that anyone who follows along can get an exact replica of the repository state (including all object hashes). The example change is the following one:
cd test/
echo 'some changes' >> README.md
git add .
export GIT_AUTHOR_NAME='Example'
export GIT_AUTHOR_EMAIL='<>'
export GIT_AUTHOR_DATE='1970-01-01T01:00:00+00:00'
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git commit -m update
Now we can push with:
SSLKEYLOGFILE=/tmp/sslkeylogfile GIT_TRACE_PACKET=true \
git -c http.sslVerify=false push
client > server: At first, the client requests the server-side receive-pack function (without any credentials):GET /git/test.git/info/refs?service=git-receive-pack HTTP/1.1 ...server > client: Since no credentials were specified by the client, the server in the specified configuration rejects the push and answers with:HTTP/1.1 401 Unauthorized ...client > server: The client tries the initial request again, this time with http basic auth credentials fortest:test(dGVzdDp0ZXN0is just base64 fortest:test, these credentials were saved from the initial clone url):GET /git/test.git/info/refs?service=git-receive-pack HTTP/1.1 ... Authorization: Basic dGVzdDp0ZXN0 ...server > client: Now the server responds with its current main branch and the capabilities it supports:001f b"# service=git-receive-pack\n" 0000 00b3 b"62cb84d4b62cc9db3329a94ffc3a4800640d039f refs/heads/master\x00" b"report-status report-status-v2 delete-refs side-band-64k quiet " b"atomic ofs-delta object-format=sha1 agent=git/2.47.3\n" 0000The individual options have the following meaning (
man gitprotocol-capabilities):report-statusandreport-status-v2: Server supports sending extra status information.delete-refs: Client can delete references on the server (e.g. delete a remote branch).side-band-64k: Server supports receiving multiplexed pack-files in larger chunks than the old default.quiet: Server supports not sending any non-essential progress reports.atomic: Server supports handling the whole push update as one single atomic transaction (all-or-nothing).ofs-delta: Server supports receiving pack-files delta-encoded against internal relative pack-file offsets (refer to section above about delta encoding of pack-files).object-format=sha1: Server uses SHA1 as hash-function for identifying objects.agent=git/2.47.3: Version string for the server git installation.
client > server: Client sends the actual push as a pack-file:POST /git/test.git/git-receive-pack ... 00ab b"62cb84d4b62cc9db3329a94ffc3a4800640d039f b6d98ea54a92dfe448211ba001427b4047704270 " b"refs/heads/master\x00 report-status-v2 side-band-64k object-format=sha1 agent=git/2.47.3" 0000 b"PACK\x00\x00\x00\x02\x00\x00\x00\x03\x93\nx\x9cu\x8bA\x0e\x02!\x0c\x00" b"\xef\xbc\x82\xbb\x97\xba\xd4B\x13\xe3\xcd\x87\x14Z\xa2\x89\xb8\x84\xb0" ...The individual options have the previously described meaning from step 4.
Note that the pack data follows the flush-pkt
0000directly as raw bytestream, not wrapped inside a pkt-line.server > client: Server gives client a final progress report that everything was processed correctly:0030 b"\x01000eunpack ok\n0019ok refs/heads/master\n0000" 0000
Project-links#
The source code behind git clone https://infinite-git.stfnw.de is available in that repo itself, or at github.com/stfnw/infinite-git.
The talk slides are available at github.com/stfnw/talk-understanding-git-transfer-protocols.