@@ -424,6 +424,20 @@ async def fetch_objects(
424
424
raise ConnectionException ("Unsupported protocol version: %s" % self .negotiated_protocol )
425
425
426
426
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
+ """
427
441
capabilities : set [str ] = set ()
428
442
capabilities .add ("agent=kalandra" )
429
443
@@ -441,9 +455,12 @@ async def _generate_fetch_v1_request(self, want: set[str], have: set[str] | None
441
455
yield PacketLine .data_from_string (f"want { obj } { ' ' .join (sorted (capabilities ))} " )
442
456
443
457
# Send the rest of the wants
458
+ yield PacketLine .data_from_string (f"want { obj } " ) # repeat the first want with capabilities
444
459
for obj in want_iter :
445
460
yield PacketLine .data_from_string ("want %s" % obj )
446
461
462
+ yield PacketLine .FLUSH
463
+
447
464
# Send the oids of objects we have
448
465
if have :
449
466
for obj in have :
@@ -452,8 +469,6 @@ async def _generate_fetch_v1_request(self, want: set[str], have: set[str] | None
452
469
continue
453
470
yield PacketLine .data_from_string ("have %s" % obj )
454
471
455
- # Send done
456
- yield PacketLine .FLUSH
457
472
yield PacketLine .data_from_string ("done" )
458
473
459
474
async def _fetch_objects_v1 (
@@ -472,22 +487,41 @@ async def _fetch_objects_v1(
472
487
- https://git-scm.com/docs/pack-protocol#_packfile_negotiation
473
488
"""
474
489
await self ._send_packet_transaction (self ._generate_fetch_v1_request (objects , have ))
475
-
476
490
assert self .reader is not None
477
491
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
485
493
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" )
491
525
492
526
async def _fetch_objects_v2 (
493
527
self ,
0 commit comments