Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dulwich/protocol.py: 34%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# protocol.py -- Shared parts of the git protocols
2# Copyright (C) 2008 John Carr <john.carr@unrouted.co.uk>
3# Copyright (C) 2008-2012 Jelmer Vernooij <jelmer@jelmer.uk>
4#
5# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
6# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
7# General Public License as published by the Free Software Foundation; version 2.0
8# or (at your option) any later version. You can redistribute it and/or
9# modify it under the terms of either of these two licenses.
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17# You should have received a copy of the licenses; if not, see
18# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
19# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
20# License, Version 2.0.
21#
23"""Generic functions for talking the git smart server protocol."""
25import types
26from collections.abc import Callable, Iterable, Sequence
27from io import BytesIO
28from os import SEEK_END
30import dulwich
32from .errors import GitProtocolError, HangupException
34TCP_GIT_PORT = 9418
36# Git protocol version 0 is the original Git protocol, which lacked a
37# version number until Git protocol version 1 was introduced by Brandon
38# Williams in 2017.
39#
40# Protocol version 1 is simply the original v0 protocol with the addition of
41# a single packet line, which precedes the ref advertisement, indicating the
42# protocol version being used. This was done in preparation for protocol v2.
43#
44# Git protocol version 2 was first introduced by Brandon Williams in 2018 and
45# adds many features. See the gitprotocol-v2(5) manual page for details.
46# As of 2024, Git only implements version 2 during 'git fetch' and still uses
47# version 0 during 'git push'.
48GIT_PROTOCOL_VERSIONS = [0, 1, 2]
49DEFAULT_GIT_PROTOCOL_VERSION_FETCH = 2
50DEFAULT_GIT_PROTOCOL_VERSION_SEND = 0
52ZERO_SHA = b"0" * 40
54SINGLE_ACK = 0
55MULTI_ACK = 1
56MULTI_ACK_DETAILED = 2
58# pack data
59SIDE_BAND_CHANNEL_DATA = 1
60# progress messages
61SIDE_BAND_CHANNEL_PROGRESS = 2
62# fatal error message just before stream aborts
63SIDE_BAND_CHANNEL_FATAL = 3
65CAPABILITY_ATOMIC = b"atomic"
66CAPABILITY_DEEPEN_SINCE = b"deepen-since"
67CAPABILITY_DEEPEN_NOT = b"deepen-not"
68CAPABILITY_DEEPEN_RELATIVE = b"deepen-relative"
69CAPABILITY_DELETE_REFS = b"delete-refs"
70CAPABILITY_INCLUDE_TAG = b"include-tag"
71CAPABILITY_MULTI_ACK = b"multi_ack"
72CAPABILITY_MULTI_ACK_DETAILED = b"multi_ack_detailed"
73CAPABILITY_NO_DONE = b"no-done"
74CAPABILITY_NO_PROGRESS = b"no-progress"
75CAPABILITY_OFS_DELTA = b"ofs-delta"
76CAPABILITY_QUIET = b"quiet"
77CAPABILITY_REPORT_STATUS = b"report-status"
78CAPABILITY_SHALLOW = b"shallow"
79CAPABILITY_SIDE_BAND = b"side-band"
80CAPABILITY_SIDE_BAND_64K = b"side-band-64k"
81CAPABILITY_THIN_PACK = b"thin-pack"
82CAPABILITY_AGENT = b"agent"
83CAPABILITY_SYMREF = b"symref"
84CAPABILITY_ALLOW_TIP_SHA1_IN_WANT = b"allow-tip-sha1-in-want"
85CAPABILITY_ALLOW_REACHABLE_SHA1_IN_WANT = b"allow-reachable-sha1-in-want"
86CAPABILITY_FETCH = b"fetch"
87CAPABILITY_FILTER = b"filter"
89# Magic ref that is used to attach capabilities to when
90# there are no refs. Should always be ste to ZERO_SHA.
91CAPABILITIES_REF = b"capabilities^{}"
93COMMON_CAPABILITIES = [
94 CAPABILITY_OFS_DELTA,
95 CAPABILITY_SIDE_BAND,
96 CAPABILITY_SIDE_BAND_64K,
97 CAPABILITY_AGENT,
98 CAPABILITY_NO_PROGRESS,
99]
100KNOWN_UPLOAD_CAPABILITIES = set(
101 [
102 *COMMON_CAPABILITIES,
103 CAPABILITY_THIN_PACK,
104 CAPABILITY_MULTI_ACK,
105 CAPABILITY_MULTI_ACK_DETAILED,
106 CAPABILITY_INCLUDE_TAG,
107 CAPABILITY_DEEPEN_SINCE,
108 CAPABILITY_SYMREF,
109 CAPABILITY_SHALLOW,
110 CAPABILITY_DEEPEN_NOT,
111 CAPABILITY_DEEPEN_RELATIVE,
112 CAPABILITY_ALLOW_TIP_SHA1_IN_WANT,
113 CAPABILITY_ALLOW_REACHABLE_SHA1_IN_WANT,
114 CAPABILITY_FETCH,
115 ]
116)
117KNOWN_RECEIVE_CAPABILITIES = set(
118 [
119 *COMMON_CAPABILITIES,
120 CAPABILITY_REPORT_STATUS,
121 CAPABILITY_DELETE_REFS,
122 CAPABILITY_QUIET,
123 CAPABILITY_ATOMIC,
124 ]
125)
127DEPTH_INFINITE = 0x7FFFFFFF
129NAK_LINE = b"NAK\n"
132def agent_string() -> bytes:
133 """Generate the agent string for dulwich.
135 Returns:
136 Agent string as bytes
137 """
138 return ("dulwich/" + ".".join(map(str, dulwich.__version__))).encode("ascii")
141def capability_agent() -> bytes:
142 """Generate the agent capability string.
144 Returns:
145 Agent capability with dulwich version
146 """
147 return CAPABILITY_AGENT + b"=" + agent_string()
150def capability_symref(from_ref: bytes, to_ref: bytes) -> bytes:
151 """Generate a symref capability string.
153 Args:
154 from_ref: Source reference name
155 to_ref: Target reference name
157 Returns:
158 Symref capability string
159 """
160 return CAPABILITY_SYMREF + b"=" + from_ref + b":" + to_ref
163def extract_capability_names(capabilities: Iterable[bytes]) -> set[bytes]:
164 """Extract capability names from a list of capabilities.
166 Args:
167 capabilities: List of capability strings
169 Returns:
170 Set of capability names
171 """
172 return {parse_capability(c)[0] for c in capabilities}
175def parse_capability(capability: bytes) -> tuple[bytes, bytes | None]:
176 """Parse a capability string into name and value.
178 Args:
179 capability: Capability string
181 Returns:
182 Tuple of (capability_name, capability_value)
183 """
184 parts = capability.split(b"=", 1)
185 if len(parts) == 1:
186 return (parts[0], None)
187 return (parts[0], parts[1])
190def symref_capabilities(symrefs: Iterable[tuple[bytes, bytes]]) -> list[bytes]:
191 """Generate symref capability strings from symref pairs.
193 Args:
194 symrefs: Iterable of (from_ref, to_ref) tuples
196 Returns:
197 List of symref capability strings
198 """
199 return [capability_symref(*k) for k in symrefs]
202COMMAND_DEEPEN = b"deepen"
203COMMAND_DEEPEN_SINCE = b"deepen-since"
204COMMAND_DEEPEN_NOT = b"deepen-not"
205COMMAND_SHALLOW = b"shallow"
206COMMAND_UNSHALLOW = b"unshallow"
207COMMAND_DONE = b"done"
208COMMAND_WANT = b"want"
209COMMAND_HAVE = b"have"
212def format_cmd_pkt(cmd: bytes, *args: bytes) -> bytes:
213 """Format a command packet.
215 Args:
216 cmd: Command name
217 *args: Command arguments
219 Returns:
220 Formatted command packet
221 """
222 return cmd + b" " + b"".join([(a + b"\0") for a in args])
225def parse_cmd_pkt(line: bytes) -> tuple[bytes, list[bytes]]:
226 """Parse a command packet.
228 Args:
229 line: Command line to parse
231 Returns:
232 Tuple of (command, [arguments])
233 """
234 splice_at = line.find(b" ")
235 cmd, args = line[:splice_at], line[splice_at + 1 :]
236 assert args[-1:] == b"\x00"
237 return cmd, args[:-1].split(b"\0")
240def pkt_line(data: bytes | None) -> bytes:
241 """Wrap data in a pkt-line.
243 Args:
244 data: The data to wrap, as a str or None.
245 Returns: The data prefixed with its length in pkt-line format; if data was
246 None, returns the flush-pkt ('0000').
247 """
248 if data is None:
249 return b"0000"
250 return f"{len(data) + 4:04x}".encode("ascii") + data
253def pkt_seq(*seq: bytes | None) -> bytes:
254 """Wrap a sequence of data in pkt-lines.
256 Args:
257 seq: An iterable of strings to wrap.
258 """
259 return b"".join([pkt_line(s) for s in seq]) + pkt_line(None)
262class Protocol:
263 """Class for interacting with a remote git process over the wire.
265 Parts of the git wire protocol use 'pkt-lines' to communicate. A pkt-line
266 consists of the length of the line as a 4-byte hex string, followed by the
267 payload data. The length includes the 4-byte header. The special line
268 '0000' indicates the end of a section of input and is called a 'flush-pkt'.
270 For details on the pkt-line format, see the cgit distribution:
271 Documentation/technical/protocol-common.txt
272 """
274 def __init__(
275 self,
276 read: Callable[[int], bytes],
277 write: Callable[[bytes], int | None],
278 close: Callable[[], None] | None = None,
279 report_activity: Callable[[int, str], None] | None = None,
280 ) -> None:
281 """Initialize Protocol.
283 Args:
284 read: Function to read bytes from the transport
285 write: Function to write bytes to the transport
286 close: Optional function to close the transport
287 report_activity: Optional function to report activity
288 """
289 self.read = read
290 self.write = write
291 self._close = close
292 self.report_activity = report_activity
293 self._readahead: BytesIO | None = None
295 def close(self) -> None:
296 """Close the underlying transport if a close function was provided."""
297 if self._close:
298 self._close()
300 def __enter__(self) -> "Protocol":
301 """Enter context manager."""
302 return self
304 def __exit__(
305 self,
306 exc_type: type[BaseException] | None,
307 exc_val: BaseException | None,
308 exc_tb: types.TracebackType | None,
309 ) -> None:
310 """Exit context manager and close transport."""
311 self.close()
313 def read_pkt_line(self) -> bytes | None:
314 """Reads a pkt-line from the remote git process.
316 This method may read from the readahead buffer; see unread_pkt_line.
318 Returns: The next string from the stream, without the length prefix, or
319 None for a flush-pkt ('0000') or delim-pkt ('0001').
320 """
321 if self._readahead is None:
322 read = self.read
323 else:
324 read = self._readahead.read
325 self._readahead = None
327 try:
328 sizestr = read(4)
329 if not sizestr:
330 raise HangupException
331 size = int(sizestr, 16)
332 if size == 0 or size == 1: # flush-pkt or delim-pkt
333 if self.report_activity:
334 self.report_activity(4, "read")
335 return None
336 if self.report_activity:
337 self.report_activity(size, "read")
338 pkt_contents = read(size - 4)
339 except ConnectionResetError as exc:
340 raise HangupException from exc
341 except OSError as exc:
342 raise GitProtocolError(str(exc)) from exc
343 else:
344 if len(pkt_contents) + 4 != size:
345 raise GitProtocolError(
346 f"Length of pkt read {len(pkt_contents) + 4:04x} does not match length prefix {size:04x}"
347 )
348 return pkt_contents
350 def eof(self) -> bool:
351 """Test whether the protocol stream has reached EOF.
353 Note that this refers to the actual stream EOF and not just a
354 flush-pkt.
356 Returns: True if the stream is at EOF, False otherwise.
357 """
358 try:
359 next_line = self.read_pkt_line()
360 except HangupException:
361 return True
362 self.unread_pkt_line(next_line)
363 return False
365 def unread_pkt_line(self, data: bytes | None) -> None:
366 """Unread a single line of data into the readahead buffer.
368 This method can be used to unread a single pkt-line into a fixed
369 readahead buffer.
371 Args:
372 data: The data to unread, without the length prefix.
374 Raises:
375 ValueError: If more than one pkt-line is unread.
376 """
377 if self._readahead is not None:
378 raise ValueError("Attempted to unread multiple pkt-lines.")
379 self._readahead = BytesIO(pkt_line(data))
381 def read_pkt_seq(self) -> Iterable[bytes]:
382 """Read a sequence of pkt-lines from the remote git process.
384 Returns: Yields each line of data up to but not including the next
385 flush-pkt.
386 """
387 pkt = self.read_pkt_line()
388 while pkt:
389 yield pkt
390 pkt = self.read_pkt_line()
392 def write_pkt_line(self, line: bytes | None) -> None:
393 """Sends a pkt-line to the remote git process.
395 Args:
396 line: A string containing the data to send, without the length
397 prefix.
398 """
399 try:
400 line = pkt_line(line)
401 self.write(line)
402 if self.report_activity:
403 self.report_activity(len(line), "write")
404 except OSError as exc:
405 raise GitProtocolError(str(exc)) from exc
407 def write_sideband(self, channel: int, blob: bytes) -> None:
408 """Write multiplexed data to the sideband.
410 Args:
411 channel: An int specifying the channel to write to.
412 blob: A blob of data (as a string) to send on this channel.
413 """
414 # a pktline can be a max of 65520. a sideband line can therefore be
415 # 65520-5 = 65515
416 # WTF: Why have the len in ASCII, but the channel in binary.
417 while blob:
418 self.write_pkt_line(bytes(bytearray([channel])) + blob[:65515])
419 blob = blob[65515:]
421 def send_cmd(self, cmd: bytes, *args: bytes) -> None:
422 """Send a command and some arguments to a git server.
424 Only used for the TCP git protocol (git://).
426 Args:
427 cmd: The remote service to access.
428 args: List of arguments to send to remove service.
429 """
430 self.write_pkt_line(format_cmd_pkt(cmd, *args))
432 def read_cmd(self) -> tuple[bytes, list[bytes]]:
433 """Read a command and some arguments from the git client.
435 Only used for the TCP git protocol (git://).
437 Returns: A tuple of (command, [list of arguments]).
438 """
439 line = self.read_pkt_line()
440 if line is None:
441 raise GitProtocolError("Expected command, got flush packet")
442 return parse_cmd_pkt(line)
445_RBUFSIZE = 65536 # 64KB buffer for better network I/O performance
448class ReceivableProtocol(Protocol):
449 """Variant of Protocol that allows reading up to a size without blocking.
451 This class has a recv() method that behaves like socket.recv() in addition
452 to a read() method.
454 If you want to read n bytes from the wire and block until exactly n bytes
455 (or EOF) are read, use read(n). If you want to read at most n bytes from
456 the wire but don't care if you get less, use recv(n). Note that recv(n)
457 will still block until at least one byte is read.
458 """
460 def __init__(
461 self,
462 recv: Callable[[int], bytes],
463 write: Callable[[bytes], int | None],
464 close: Callable[[], None] | None = None,
465 report_activity: Callable[[int, str], None] | None = None,
466 rbufsize: int = _RBUFSIZE,
467 ) -> None:
468 """Initialize ReceivableProtocol.
470 Args:
471 recv: Function to receive bytes from the transport
472 write: Function to write bytes to the transport
473 close: Optional function to close the transport
474 report_activity: Optional function to report activity
475 rbufsize: Read buffer size
476 """
477 super().__init__(self.read, write, close=close, report_activity=report_activity)
478 self._recv = recv
479 self._rbuf = BytesIO()
480 self._rbufsize = rbufsize
482 def read(self, size: int) -> bytes:
483 """Read bytes from the socket.
485 Args:
486 size: Number of bytes to read
488 Returns:
489 Bytes read from socket
490 """
491 # From _fileobj.read in socket.py in the Python 2.6.5 standard library,
492 # with the following modifications:
493 # - omit the size <= 0 branch
494 # - seek back to start rather than 0 in case some buffer has been
495 # consumed.
496 # - use SEEK_END instead of the magic number.
497 # Copyright (c) 2001-2010 Python Software Foundation; All Rights
498 # Reserved
499 # Licensed under the Python Software Foundation License.
500 # TODO: see if buffer is more efficient than cBytesIO.
501 assert size > 0
503 # Our use of BytesIO rather than lists of string objects returned by
504 # recv() minimizes memory usage and fragmentation that occurs when
505 # rbufsize is large compared to the typical return value of recv().
506 buf = self._rbuf
507 start = buf.tell()
508 buf.seek(0, SEEK_END)
509 # buffer may have been partially consumed by recv()
510 buf_len = buf.tell() - start
511 if buf_len >= size:
512 # Already have size bytes in our buffer? Extract and return.
513 buf.seek(start)
514 rv = buf.read(size)
515 self._rbuf = BytesIO()
516 self._rbuf.write(buf.read())
517 self._rbuf.seek(0)
518 return rv
520 self._rbuf = BytesIO() # reset _rbuf. we consume it via buf.
521 while True:
522 left = size - buf_len
523 # recv() will malloc the amount of memory given as its
524 # parameter even though it often returns much less data
525 # than that. The returned data string is short lived
526 # as we copy it into a BytesIO and free it. This avoids
527 # fragmentation issues on many platforms.
528 data = self._recv(left)
529 if not data:
530 break
531 n = len(data)
532 if n == size and not buf_len:
533 # Shortcut. Avoid buffer data copies when:
534 # - We have no data in our buffer.
535 # AND
536 # - Our call to recv returned exactly the
537 # number of bytes we were asked to read.
538 return data
539 if n == left:
540 buf.write(data)
541 del data # explicit free
542 break
543 assert n <= left, f"_recv({left}) returned {n} bytes"
544 buf.write(data)
545 buf_len += n
546 del data # explicit free
547 # assert buf_len == buf.tell()
548 buf.seek(start)
549 return buf.read()
551 def recv(self, size: int) -> bytes:
552 """Receive bytes from the socket with buffering.
554 Args:
555 size: Maximum number of bytes to receive
557 Returns:
558 Bytes received from socket
559 """
560 assert size > 0
562 buf = self._rbuf
563 start = buf.tell()
564 buf.seek(0, SEEK_END)
565 buf_len = buf.tell()
566 buf.seek(start)
568 left = buf_len - start
569 if not left:
570 # only read from the wire if our read buffer is exhausted
571 data = self._recv(self._rbufsize)
572 if len(data) == size:
573 # shortcut: skip the buffer if we read exactly size bytes
574 return data
575 buf = BytesIO()
576 buf.write(data)
577 buf.seek(0)
578 del data # explicit free
579 self._rbuf = buf
580 return buf.read(size)
583def extract_capabilities(text: bytes) -> tuple[bytes, list[bytes]]:
584 """Extract a capabilities list from a string, if present.
586 Args:
587 text: String to extract from
588 Returns: Tuple with text with capabilities removed and list of capabilities
589 """
590 if b"\0" not in text:
591 return text, []
592 text, capabilities = text.rstrip().split(b"\0")
593 return (text, capabilities.strip().split(b" "))
596def extract_want_line_capabilities(text: bytes) -> tuple[bytes, list[bytes]]:
597 """Extract a capabilities list from a want line, if present.
599 Note that want lines have capabilities separated from the rest of the line
600 by a space instead of a null byte. Thus want lines have the form:
602 want obj-id cap1 cap2 ...
604 Args:
605 text: Want line to extract from
606 Returns: Tuple with text with capabilities removed and list of capabilities
607 """
608 split_text = text.rstrip().split(b" ")
609 if len(split_text) < 3:
610 return text, []
611 return (b" ".join(split_text[:2]), split_text[2:])
614def ack_type(capabilities: Iterable[bytes]) -> int:
615 """Extract the ack type from a capabilities list."""
616 if b"multi_ack_detailed" in capabilities:
617 return MULTI_ACK_DETAILED
618 elif b"multi_ack" in capabilities:
619 return MULTI_ACK
620 return SINGLE_ACK
623class BufferedPktLineWriter:
624 """Writer that wraps its data in pkt-lines and has an independent buffer.
626 Consecutive calls to write() wrap the data in a pkt-line and then buffers
627 it until enough lines have been written such that their total length
628 (including length prefix) reach the buffer size.
629 """
631 def __init__(
632 self, write: Callable[[bytes], int | None], bufsize: int = 65515
633 ) -> None:
634 """Initialize the BufferedPktLineWriter.
636 Args:
637 write: A write callback for the underlying writer.
638 bufsize: The internal buffer size, including length prefixes.
639 """
640 self._write = write
641 self._bufsize = bufsize
642 self._wbuf = BytesIO()
643 self._buflen = 0
645 def write(self, data: bytes) -> None:
646 """Write data, wrapping it in a pkt-line."""
647 line = pkt_line(data)
648 line_len = len(line)
649 over = self._buflen + line_len - self._bufsize
650 if over >= 0:
651 start = line_len - over
652 self._wbuf.write(line[:start])
653 self.flush()
654 else:
655 start = 0
656 saved = line[start:]
657 self._wbuf.write(saved)
658 self._buflen += len(saved)
660 def flush(self) -> None:
661 """Flush all data from the buffer."""
662 data = self._wbuf.getvalue()
663 if data:
664 self._write(data)
665 self._len = 0
666 self._wbuf = BytesIO()
669class PktLineParser:
670 """Packet line parser that hands completed packets off to a callback."""
672 def __init__(self, handle_pkt: Callable[[bytes | None], None]) -> None:
673 """Initialize PktLineParser.
675 Args:
676 handle_pkt: Callback function to handle completed packets
677 """
678 self.handle_pkt = handle_pkt
679 self._readahead = BytesIO()
681 def parse(self, data: bytes) -> None:
682 """Parse a fragment of data and call back for any completed packets."""
683 self._readahead.write(data)
684 buf = self._readahead.getvalue()
685 if len(buf) < 4:
686 return
687 while len(buf) >= 4:
688 size = int(buf[:4], 16)
689 if size == 0:
690 self.handle_pkt(None)
691 buf = buf[4:]
692 elif size <= len(buf):
693 self.handle_pkt(buf[4:size])
694 buf = buf[size:]
695 else:
696 break
697 self._readahead = BytesIO()
698 self._readahead.write(buf)
700 def get_tail(self) -> bytes:
701 """Read back any unused data."""
702 return self._readahead.getvalue()
705def format_capability_line(capabilities: Iterable[bytes]) -> bytes:
706 """Format a capabilities list for the wire protocol.
708 Args:
709 capabilities: List of capability strings
711 Returns:
712 Space-separated capabilities as bytes
713 """
714 return b"".join([b" " + c for c in capabilities])
717def format_ref_line(
718 ref: bytes, sha: bytes, capabilities: Sequence[bytes] | None = None
719) -> bytes:
720 """Format a ref advertisement line.
722 Args:
723 ref: Reference name
724 sha: SHA hash
725 capabilities: Optional list of capabilities
727 Returns:
728 Formatted ref line
729 """
730 if capabilities is None:
731 return sha + b" " + ref + b"\n"
732 else:
733 return sha + b" " + ref + b"\0" + format_capability_line(capabilities) + b"\n"
736def format_shallow_line(sha: bytes) -> bytes:
737 """Format a shallow line.
739 Args:
740 sha: SHA to mark as shallow
742 Returns:
743 Formatted shallow line
744 """
745 return COMMAND_SHALLOW + b" " + sha
748def format_unshallow_line(sha: bytes) -> bytes:
749 """Format an unshallow line.
751 Args:
752 sha: SHA to unshallow
754 Returns:
755 Formatted unshallow line
756 """
757 return COMMAND_UNSHALLOW + b" " + sha
760def format_ack_line(sha: bytes, ack_type: bytes = b"") -> bytes:
761 """Format an ACK line.
763 Args:
764 sha: SHA to acknowledge
765 ack_type: Optional ACK type (e.g. b"continue")
767 Returns:
768 Formatted ACK line
769 """
770 if ack_type:
771 ack_type = b" " + ack_type
772 return b"ACK " + sha + ack_type + b"\n"