Skip to content

Commit d3d428a

Browse files
authored
fix: send FLUSH in v1 of pack negotations in correct place (#19)
1 parent 3a60292 commit d3d428a

File tree

1 file changed

+49
-15
lines changed

1 file changed

+49
-15
lines changed

src/kalandra/transports/base.py

+49-15
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,20 @@ async def fetch_objects(
424424
raise ConnectionException("Unsupported protocol version: %s" % self.negotiated_protocol)
425425

426426
async def _generate_fetch_v1_request(self, want: set[str], have: set[str] | None) -> AsyncIterator[PacketLine]:
427+
"""
428+
429+
```
430+
005dwant 7e851ed9ae595e2318464727c062062783c231f6 multi_ack_detailed side-band-64k thin-pack
431+
00000032have 5a6d54728a8f0f8184cf1749dd16a3ca7df78c2a
432+
0032have 2d9b7bde6e6058cf7ae781ddcc80e2df6a9d78c2
433+
0032have fd8ef3e8789e408ebcb38f62be1f37c742013bac
434+
0032have fc5a665bfdb2d80f0dbb03d9a51cb86aad679061
435+
0032have 2f77028723f425e0c5692828a099527a413187bf
436+
0032have 15dcdca4f7700d3ab8302986d665626349b548f6
437+
0032have 68c556244e4f2ff941e472a6589246127a9f94dd
438+
0009done
439+
```
440+
"""
427441
capabilities: set[str] = set()
428442
capabilities.add("agent=kalandra")
429443

@@ -441,9 +455,12 @@ async def _generate_fetch_v1_request(self, want: set[str], have: set[str] | None
441455
yield PacketLine.data_from_string(f"want {obj} {' '.join(sorted(capabilities))}")
442456

443457
# Send the rest of the wants
458+
yield PacketLine.data_from_string(f"want {obj}") # repeat the first want with capabilities
444459
for obj in want_iter:
445460
yield PacketLine.data_from_string("want %s" % obj)
446461

462+
yield PacketLine.FLUSH
463+
447464
# Send the oids of objects we have
448465
if have:
449466
for obj in have:
@@ -452,8 +469,6 @@ async def _generate_fetch_v1_request(self, want: set[str], have: set[str] | None
452469
continue
453470
yield PacketLine.data_from_string("have %s" % obj)
454471

455-
# Send done
456-
yield PacketLine.FLUSH
457472
yield PacketLine.data_from_string("done")
458473

459474
async def _fetch_objects_v1(
@@ -472,22 +487,41 @@ async def _fetch_objects_v1(
472487
- https://git-scm.com/docs/pack-protocol#_packfile_negotiation
473488
"""
474489
await self._send_packet_transaction(self._generate_fetch_v1_request(objects, have))
475-
476490
assert self.reader is not None
477491

478-
pkt = await self._read_packet()
479-
while pkt.data.startswith(b"ACK "):
480-
# we will zero or more ACKs - we don't care about them
481-
pkt = await self._read_packet()
482-
483-
if pkt.data.rstrip() != b"NAK":
484-
raise ConnectionError("Expected NAK packet, instead got: %r" % pkt.data)
492+
expect_pack = False
485493

486-
# The rest of the response is the packfile
487-
async for chunk in self.reader:
488-
await output.write(chunk)
489-
490-
await output.flush()
494+
# https://git-scm.com/docs/pack-protocol#_packfile_negotiation
495+
#
496+
# Once the done line is read from the client, the server will either send a final ACK obj-id
497+
# or it will send a NAK. obj-id is the object name of the last commit determined to be common.
498+
# The server only sends ACK after done if there is at least one common base and multi_ack or multi_ack_detailed is enabled.
499+
# The server always sends NAK after done if there is no common base found.
500+
while not expect_pack:
501+
pkt = await self._read_packet()
502+
logger.debug("Incoming packet: %s", pkt)
503+
504+
if pkt.data.startswith(b"ACK "):
505+
parts = pkt.data.split(b" ", 3)
506+
# the last ACK
507+
if len(parts) == 2:
508+
logger.debug("Last ACK received")
509+
expect_pack = True
510+
elif pkt.data.startswith(b"NAK"):
511+
logger.debug("NAK received, proceeding with packfile")
512+
expect_pack = True
513+
else:
514+
# anything else is an error message
515+
raise ConnectionException(f"Unexpected packet: {pkt}")
516+
517+
if expect_pack:
518+
# The rest of the response is the packfile
519+
async for chunk in self.reader:
520+
await output.write(chunk)
521+
522+
await output.flush()
523+
else:
524+
raise ConnectionException("No packfile received from server")
491525

492526
async def _fetch_objects_v2(
493527
self,

0 commit comments

Comments
 (0)