1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
4
5use sley_core::ObjectFormat;
6use sley_core::Result;
7use sley_odb::{FileObjectDatabase, RawPackInstallOptions, RawPackInstallResult, RawPackInstaller};
8use sley_protocol::{
9 PktLineFrame, ProtocolV2FetchResponseHeader, ProtocolV2FetchResponseSection,
10 ProtocolV2FetchShallowInfo, SideBandChannel, demux_upload_pack_packfile_response,
11 parse_sideband_packet, read_pkt_line_frame, read_protocol_v2_fetch_response_header,
12 read_upload_pack_packfile_response, read_upload_pack_raw_packfile_response_header,
13 read_upload_pack_shallow_info_section,
14 read_upload_pack_shallow_info_and_raw_packfile_response_header,
15};
16use std::io::{Cursor, ErrorKind, Read};
17
18fn raw_pack_install_options(
19 promisor: bool,
20 max_input_size: Option<u64>,
21) -> RawPackInstallOptions {
22 RawPackInstallOptions {
23 promisor,
24 max_input_size,
25 }
26}
27
28pub fn install_upload_pack_raw_response_from_reader<I, R>(
29 format: ObjectFormat,
30 reader: &mut R,
31 destination: &I,
32 max_input_size: Option<u64>,
33) -> Result<RawPackInstallResult>
34where
35 I: RawPackInstaller,
36 R: Read,
37{
38 let header = read_upload_pack_raw_packfile_response_header(format, reader)?;
39 let mut pack_reader = Cursor::new(header.pack_prefix).chain(reader);
40 destination.install_raw_pack_from_reader_with_options(
41 &mut pack_reader,
42 raw_pack_install_options(false, max_input_size),
43 )
44}
45
46pub fn install_upload_pack_packfile_response_from_reader<I, R>(
51 format: ObjectFormat,
52 reader: &mut R,
53 destination: &I,
54 max_input_size: Option<u64>,
55) -> Result<RawPackInstallResult>
56where
57 I: RawPackInstaller,
58 R: Read,
59{
60 install_upload_pack_sideband_response_from_reader(format, reader, destination, false, max_input_size)
61}
62
63pub fn install_upload_pack_packfile_promisor_response_from_reader<R>(
64 format: ObjectFormat,
65 reader: &mut R,
66 destination: &FileObjectDatabase,
67 max_input_size: Option<u64>,
68) -> Result<RawPackInstallResult>
69where
70 R: Read,
71{
72 install_upload_pack_sideband_response_from_reader(format, reader, destination, true, max_input_size)
73}
74
75fn install_upload_pack_sideband_response_from_reader<I, R>(
76 format: ObjectFormat,
77 reader: &mut R,
78 destination: &I,
79 promisor: bool,
80 max_input_size: Option<u64>,
81) -> Result<RawPackInstallResult>
82where
83 I: RawPackInstaller,
84 R: Read,
85{
86 let response = read_upload_pack_packfile_response(format, reader)?;
87 let demuxed = demux_upload_pack_packfile_response(&response)?;
88 let mut pack_reader = demuxed.data.as_slice();
89 let result = destination.install_raw_pack_from_reader_with_options(
90 &mut pack_reader,
91 raw_pack_install_options(promisor, max_input_size),
92 )?;
93 Ok(if promisor {
94 RawPackInstallResult {
95 object_ids: result.object_ids,
96 }
97 } else {
98 result
99 })
100}
101
102pub fn install_upload_pack_raw_promisor_response_from_reader<R>(
103 format: ObjectFormat,
104 reader: &mut R,
105 destination: &FileObjectDatabase,
106 max_input_size: Option<u64>,
107) -> Result<RawPackInstallResult>
108where
109 R: Read,
110{
111 let header = read_upload_pack_raw_packfile_response_header(format, reader)?;
112 let mut pack_reader = Cursor::new(header.pack_prefix).chain(reader);
113 let result = destination.install_raw_pack_from_reader_with_options(
114 &mut pack_reader,
115 raw_pack_install_options(true, max_input_size),
116 )?;
117 Ok(RawPackInstallResult {
118 object_ids: result.object_ids,
119 })
120}
121
122pub fn install_upload_pack_shallow_raw_response_from_reader<I, R>(
123 format: ObjectFormat,
124 reader: &mut R,
125 destination: &I,
126 max_input_size: Option<u64>,
127) -> Result<(Vec<ProtocolV2FetchShallowInfo>, RawPackInstallResult)>
128where
129 I: RawPackInstaller,
130 R: Read,
131{
132 let (shallow, header) =
133 read_upload_pack_shallow_info_and_raw_packfile_response_header(format, reader)?;
134 let mut pack_reader = Cursor::new(header.pack_prefix).chain(reader);
135 let result = destination.install_raw_pack_from_reader_with_options(
136 &mut pack_reader,
137 raw_pack_install_options(false, max_input_size),
138 )?;
139 Ok((shallow, result))
140}
141
142pub fn install_upload_pack_shallow_packfile_response_from_reader<I, R>(
144 format: ObjectFormat,
145 reader: &mut R,
146 destination: &I,
147 max_input_size: Option<u64>,
148) -> Result<(Vec<ProtocolV2FetchShallowInfo>, RawPackInstallResult)>
149where
150 I: RawPackInstaller,
151 R: Read,
152{
153 install_upload_pack_shallow_sideband_response_from_reader(
154 format,
155 reader,
156 destination,
157 false,
158 max_input_size,
159 )
160}
161
162fn install_upload_pack_shallow_sideband_response_from_reader<I, R>(
163 format: ObjectFormat,
164 reader: &mut R,
165 destination: &I,
166 promisor: bool,
167 max_input_size: Option<u64>,
168) -> Result<(Vec<ProtocolV2FetchShallowInfo>, RawPackInstallResult)>
169where
170 I: RawPackInstaller,
171 R: Read,
172{
173 let shallow = read_upload_pack_shallow_info_section(format, reader)?;
174 let result =
175 install_upload_pack_sideband_response_from_reader(format, reader, destination, promisor, max_input_size)?;
176 Ok((shallow, result))
177}
178
179pub fn install_upload_pack_shallow_raw_promisor_response_from_reader<R>(
180 format: ObjectFormat,
181 reader: &mut R,
182 destination: &FileObjectDatabase,
183 max_input_size: Option<u64>,
184) -> Result<(Vec<ProtocolV2FetchShallowInfo>, RawPackInstallResult)>
185where
186 R: Read,
187{
188 let (shallow, header) =
189 read_upload_pack_shallow_info_and_raw_packfile_response_header(format, reader)?;
190 let mut pack_reader = Cursor::new(header.pack_prefix).chain(reader);
191 let result = destination.install_raw_pack_from_reader_with_options(
192 &mut pack_reader,
193 raw_pack_install_options(true, max_input_size),
194 )?;
195 Ok((
196 shallow,
197 RawPackInstallResult {
198 object_ids: result.object_ids,
199 },
200 ))
201}
202
203pub fn install_upload_pack_shallow_packfile_promisor_response_from_reader<R>(
204 format: ObjectFormat,
205 reader: &mut R,
206 destination: &FileObjectDatabase,
207 max_input_size: Option<u64>,
208) -> Result<(Vec<ProtocolV2FetchShallowInfo>, RawPackInstallResult)>
209where
210 R: Read,
211{
212 install_upload_pack_shallow_sideband_response_from_reader(
213 format,
214 reader,
215 destination,
216 true,
217 max_input_size,
218 )
219}
220
221pub fn install_protocol_v2_fetch_response_from_reader<I, R>(
222 format: ObjectFormat,
223 reader: &mut R,
224 sideband_all: bool,
225 destination: &I,
226 max_input_size: Option<u64>,
227) -> Result<(ProtocolV2FetchResponseHeader, Option<RawPackInstallResult>)>
228where
229 I: RawPackInstaller,
230 R: Read,
231{
232 let header = read_protocol_v2_fetch_response_header(format, reader, sideband_all)?;
233 if !header.has_packfile {
234 return Ok((header, None));
235 }
236 let mut pack_reader = ProtocolV2PackfileReader::new(reader);
237 let result = destination.install_raw_pack_from_reader_with_options(
238 &mut pack_reader,
239 raw_pack_install_options(false, max_input_size),
240 )?;
241 Ok((header, Some(result)))
242}
243
244pub fn install_protocol_v2_fetch_promisor_response_from_reader<R>(
245 format: ObjectFormat,
246 reader: &mut R,
247 sideband_all: bool,
248 destination: &FileObjectDatabase,
249 max_input_size: Option<u64>,
250) -> Result<(ProtocolV2FetchResponseHeader, Option<RawPackInstallResult>)>
251where
252 R: Read,
253{
254 let header = read_protocol_v2_fetch_response_header(format, reader, sideband_all)?;
255 if !header.has_packfile {
256 return Ok((header, None));
257 }
258 let mut pack_reader = ProtocolV2PackfileReader::new(reader);
259 let result = destination.install_raw_pack_from_reader_with_options(
260 &mut pack_reader,
261 raw_pack_install_options(true, max_input_size),
262 )?;
263 Ok((
264 header,
265 Some(RawPackInstallResult {
266 object_ids: result.object_ids,
267 }),
268 ))
269}
270
271struct ProtocolV2PackfileReader<'a, R> {
272 reader: &'a mut R,
273 pending: Vec<u8>,
274 pending_offset: usize,
275 done: bool,
276}
277
278impl<'a, R> ProtocolV2PackfileReader<'a, R> {
279 fn new(reader: &'a mut R) -> Self {
280 Self {
281 reader,
282 pending: Vec::new(),
283 pending_offset: 0,
284 done: false,
285 }
286 }
287
288 fn drain_pending(&mut self, buf: &mut [u8]) -> usize {
289 let available = self.pending.len().saturating_sub(self.pending_offset);
290 let to_copy = available.min(buf.len());
291 if to_copy == 0 {
292 if self.pending_offset >= self.pending.len() {
293 self.pending.clear();
294 self.pending_offset = 0;
295 }
296 return 0;
297 }
298 let end = self.pending_offset + to_copy;
299 buf[..to_copy].copy_from_slice(&self.pending[self.pending_offset..end]);
300 self.pending_offset = end;
301 if self.pending_offset == self.pending.len() {
302 self.pending.clear();
303 self.pending_offset = 0;
304 }
305 to_copy
306 }
307}
308
309impl<R> Read for ProtocolV2PackfileReader<'_, R>
310where
311 R: Read,
312{
313 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
314 if buf.is_empty() {
315 return Ok(0);
316 }
317 let mut written = 0usize;
318 while written < buf.len() {
319 let copied = self.drain_pending(&mut buf[written..]);
320 if copied > 0 {
321 written += copied;
322 continue;
323 }
324 if self.done {
325 break;
326 }
327 let frame = read_pkt_line_frame(self.reader)
328 .map_err(git_error_to_io)?
329 .ok_or_else(|| {
330 std::io::Error::new(
331 ErrorKind::UnexpectedEof,
332 "protocol v2 packfile ended before flush",
333 )
334 })?;
335 match frame {
336 PktLineFrame::Data(payload) => {
337 let packet = parse_sideband_packet(&payload).map_err(git_error_to_io)?;
338 match packet.channel {
339 SideBandChannel::Data => {
340 self.pending = packet.data;
341 self.pending_offset = 0;
342 }
343 SideBandChannel::Progress => {}
344 SideBandChannel::Fatal => {
345 let message = String::from_utf8_lossy(&packet.data).into_owned();
346 return Err(std::io::Error::new(
347 ErrorKind::InvalidData,
348 format!("sideband fatal: {message}"),
349 ));
350 }
351 }
352 }
353 PktLineFrame::Flush => {
354 self.done = true;
355 break;
356 }
357 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
358 return Err(std::io::Error::new(
359 ErrorKind::InvalidData,
360 "protocol v2 packfile section ended unexpectedly",
361 ));
362 }
363 }
364 }
365 Ok(written)
366 }
367}
368
369fn git_error_to_io(err: sley_core::GitError) -> std::io::Error {
370 std::io::Error::new(ErrorKind::InvalidData, err.to_string())
371}
372
373pub fn shallow_info_from_protocol_v2_fetch_header(
374 header: &ProtocolV2FetchResponseHeader,
375) -> Vec<ProtocolV2FetchShallowInfo> {
376 let mut shallow_info = Vec::new();
377 for section in &header.sections {
378 if let ProtocolV2FetchResponseSection::ShallowInfo(entries) = section {
379 shallow_info.extend(entries.clone());
380 }
381 }
382 shallow_info
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use sley_core::ObjectFormat;
389 use sley_object::{EncodedObject, ObjectType};
390 use sley_odb::{FileObjectDatabase, ObjectDatabase, ObjectReader};
391 use sley_pack::PackFile;
392 use sley_protocol::{
393 SideBandChannel, SideBandPacket, UploadPackAcknowledgment, UploadPackRawPackfileResponse,
394 encode_sideband_packet, encode_upload_pack_raw_packfile_response, write_pkt_line_payload,
395 write_protocol_v2_fetch_response, write_upload_pack_raw_packfile_response,
396 };
397 use std::fs;
398 use std::path::Path;
399 use std::sync::atomic::{AtomicU64, Ordering};
400
401 static TEST_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
402
403 #[test]
404 fn raw_upload_pack_response_stream_installs_pack_without_buffering_response() {
405 let root = test_temp_root("sley-remote-install-upload-pack-raw-stream-install");
406 let format = ObjectFormat::Sha1;
407 let object = EncodedObject::new(ObjectType::Blob, b"raw streamed upload-pack\n".to_vec());
408 let oid = object
409 .object_id(format)
410 .expect("test operation should succeed");
411 let pack = PackFile::write_undeltified(std::slice::from_ref(&object), format)
412 .expect("test operation should succeed");
413 let response = UploadPackRawPackfileResponse {
414 acknowledgments: vec![UploadPackAcknowledgment::Nak],
415 packfile: pack.pack,
416 };
417 let encoded =
418 encode_upload_pack_raw_packfile_response(&response).expect("response should encode");
419 let destination = FileObjectDatabase::new(root.join("objects"), format);
420 let mut reader = encoded.as_slice();
421
422 let result =
423 install_upload_pack_raw_response_from_reader(format, &mut reader, &destination, None)
424 .expect("test operation should succeed");
425
426 assert_eq!(result.object_ids, vec![oid]);
427 assert_pack_install(&root.join("objects"), &destination, &oid, &object);
428 let _ = fs::remove_dir_all(&root);
429 }
430
431 #[test]
432 fn shallow_raw_upload_pack_response_stream_installs_pack_without_buffering_response() {
433 let root = test_temp_root("sley-remote-install-upload-pack-shallow-raw-stream-install");
434 let format = ObjectFormat::Sha1;
435 let shallow_oid =
436 sley_core::ObjectId::from_hex(format, "1111111111111111111111111111111111111111")
437 .expect("test operation should succeed");
438 let object =
439 EncodedObject::new(ObjectType::Blob, b"shallow streamed upload-pack\n".to_vec());
440 let oid = object
441 .object_id(format)
442 .expect("test operation should succeed");
443 let pack = PackFile::write_undeltified(std::slice::from_ref(&object), format)
444 .expect("test operation should succeed");
445 let response = UploadPackRawPackfileResponse {
446 acknowledgments: vec![UploadPackAcknowledgment::Nak],
447 packfile: pack.pack,
448 };
449 let mut encoded = Vec::new();
450 write_pkt_line_payload(&mut encoded, format!("shallow {shallow_oid}\n").as_bytes())
451 .expect("test operation should succeed");
452 encoded.extend_from_slice(b"0000");
453 write_upload_pack_raw_packfile_response(&mut encoded, &response)
454 .expect("test operation should succeed");
455 let destination = FileObjectDatabase::new(root.join("objects"), format);
456 let mut reader = encoded.as_slice();
457
458 let (shallow, result) =
459 install_upload_pack_shallow_raw_response_from_reader(format, &mut reader, &destination, None)
460 .expect("test operation should succeed");
461
462 assert_eq!(
463 shallow,
464 vec![ProtocolV2FetchShallowInfo::Shallow(shallow_oid)]
465 );
466 assert_eq!(result.object_ids, vec![oid]);
467 assert_pack_install(&root.join("objects"), &destination, &oid, &object);
468 let _ = fs::remove_dir_all(&root);
469 }
470
471 #[test]
472 fn raw_upload_pack_response_installs_promisor_pack_sidecar() {
473 let root = test_temp_root("sley-remote-install-upload-pack-promisor-install");
474 let format = ObjectFormat::Sha1;
475 let object = EncodedObject::new(ObjectType::Blob, b"promisor upload-pack\n".to_vec());
476 let oid = object
477 .object_id(format)
478 .expect("test operation should succeed");
479 let pack = PackFile::write_undeltified(std::slice::from_ref(&object), format)
480 .expect("test operation should succeed");
481 let response = UploadPackRawPackfileResponse {
482 acknowledgments: Vec::new(),
483 packfile: pack.pack,
484 };
485 let mut encoded = Vec::new();
486 write_upload_pack_raw_packfile_response(&mut encoded, &response)
487 .expect("test operation should succeed");
488 let destination = FileObjectDatabase::new(root.join("objects"), format);
489 let mut reader = encoded.as_slice();
490
491 let result = install_upload_pack_raw_promisor_response_from_reader(
492 format,
493 &mut reader,
494 &destination,
495 None,
496 )
497 .expect("test operation should succeed");
498
499 assert_eq!(result.object_ids, vec![oid]);
500 assert_pack_install(&root.join("objects"), &destination, &oid, &object);
501 assert_promisor_sidecar(&root.join("objects"));
502 let _ = fs::remove_dir_all(&root);
503 }
504
505 #[test]
506 fn protocol_v2_fetch_response_packfile_demuxes_and_installs_pack() {
507 let root = test_temp_root("sley-remote-install-v2-response-install");
508 let format = ObjectFormat::Sha1;
509 let object = EncodedObject::new(ObjectType::Blob, b"v2 response packfile\n".to_vec());
510 let oid = object
511 .object_id(format)
512 .expect("test operation should succeed");
513 let pack = PackFile::write_undeltified(std::slice::from_ref(&object), format)
514 .expect("test operation should succeed");
515 let sections = vec![ProtocolV2FetchResponseSection::Packfile(vec![
516 encode_sideband_packet(&SideBandPacket {
517 channel: SideBandChannel::Progress,
518 data: b"counting objects\n".to_vec(),
519 })
520 .expect("test operation should succeed"),
521 encode_sideband_packet(&SideBandPacket {
522 channel: SideBandChannel::Data,
523 data: pack.pack,
524 })
525 .expect("test operation should succeed"),
526 ])];
527 let mut encoded = Vec::new();
528 write_protocol_v2_fetch_response(&mut encoded, §ions)
529 .expect("test operation should succeed");
530 let destination = FileObjectDatabase::new(root.join("objects"), format);
531 let mut reader = encoded.as_slice();
532
533 let (header, result) = install_protocol_v2_fetch_response_from_reader(
534 format,
535 &mut reader,
536 false,
537 &destination,
538 None,
539 )
540 .expect("test operation should succeed");
541 let result = result.expect("packfile should be installed");
542
543 assert!(header.has_packfile);
544 assert!(header.sections.is_empty());
545 assert_eq!(result.object_ids, vec![oid]);
546 assert_pack_install(&root.join("objects"), &destination, &oid, &object);
547 let _ = fs::remove_dir_all(&root);
548 }
549
550 #[test]
551 fn protocol_v2_fetch_response_packfile_installs_promisor_sidecar() {
552 let root = test_temp_root("sley-remote-install-v2-response-promisor-install");
553 let format = ObjectFormat::Sha1;
554 let object = EncodedObject::new(ObjectType::Blob, b"v2 promisor packfile\n".to_vec());
555 let oid = object
556 .object_id(format)
557 .expect("test operation should succeed");
558 let pack = PackFile::write_undeltified(std::slice::from_ref(&object), format)
559 .expect("test operation should succeed");
560 let sections = vec![ProtocolV2FetchResponseSection::Packfile(vec![
561 encode_sideband_packet(&SideBandPacket {
562 channel: SideBandChannel::Data,
563 data: pack.pack,
564 })
565 .expect("test operation should succeed"),
566 ])];
567 let mut encoded = Vec::new();
568 write_protocol_v2_fetch_response(&mut encoded, §ions)
569 .expect("test operation should succeed");
570 let destination = FileObjectDatabase::new(root.join("objects"), format);
571 let mut reader = encoded.as_slice();
572
573 let (header, result) = install_protocol_v2_fetch_promisor_response_from_reader(
574 format,
575 &mut reader,
576 false,
577 &destination,
578 None,
579 )
580 .expect("test operation should succeed");
581 let result = result.expect("packfile should be installed");
582
583 assert!(header.has_packfile);
584 assert_eq!(result.object_ids, vec![oid]);
585 assert_pack_install(&root.join("objects"), &destination, &oid, &object);
586 assert_promisor_sidecar(&root.join("objects"));
587 let _ = fs::remove_dir_all(&root);
588 }
589
590 #[test]
591 fn protocol_v2_fetch_response_without_packfile_installs_nothing() {
592 let root = test_temp_root("sley-remote-install-v2-response-empty");
593 let destination = FileObjectDatabase::new(root.join("objects"), ObjectFormat::Sha1);
594 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(Vec::new())];
595 let mut encoded = Vec::new();
596 write_protocol_v2_fetch_response(&mut encoded, §ions)
597 .expect("test operation should succeed");
598 let mut reader = encoded.as_slice();
599
600 let (header, result) = install_protocol_v2_fetch_response_from_reader(
601 ObjectFormat::Sha1,
602 &mut reader,
603 false,
604 &destination,
605 None,
606 )
607 .expect("test operation should succeed");
608
609 assert!(!header.has_packfile);
610 assert!(result.is_none());
611 assert!(!root.join("objects").join("pack").exists());
612 let _ = fs::remove_dir_all(&root);
613 }
614
615 #[test]
616 fn raw_upload_pack_response_rejects_pack_exceeding_max_input_size() {
617 let root = test_temp_root("sley-remote-install-upload-pack-max-size");
618 let format = ObjectFormat::Sha1;
619 let object = EncodedObject::new(ObjectType::Blob, b"oversized fetch pack\n".to_vec());
620 let pack = PackFile::write_undeltified(std::slice::from_ref(&object), format)
621 .expect("test operation should succeed");
622 let response = UploadPackRawPackfileResponse {
623 acknowledgments: Vec::new(),
624 packfile: pack.pack,
625 };
626 let encoded =
627 encode_upload_pack_raw_packfile_response(&response).expect("response should encode");
628 let destination = FileObjectDatabase::new(root.join("objects"), format);
629 let mut reader = encoded.as_slice();
630 let limit = 32u64;
631
632 let err = install_upload_pack_raw_response_from_reader(
633 format,
634 &mut reader,
635 &destination,
636 Some(limit),
637 )
638 .expect_err("oversized pack should be rejected");
639
640 assert!(
641 err.to_string().contains("pack exceeds maximum allowed size"),
642 "unexpected error: {err}"
643 );
644 let pack_dir = root.join("objects").join("pack");
645 let installed = fs::read_dir(&pack_dir)
646 .map(|entries| {
647 entries
648 .filter_map(|entry| entry.ok())
649 .filter(|entry| {
650 entry
651 .path()
652 .extension()
653 .and_then(|ext| ext.to_str())
654 == Some("pack")
655 })
656 .count()
657 })
658 .unwrap_or_default();
659 assert_eq!(installed, 0);
660 let _ = fs::remove_dir_all(&root);
661 }
662
663 #[test]
664 fn install_helpers_accept_custom_raw_pack_installer() {
665 #[derive(Default)]
666 struct RecordingInstaller {
667 packs: std::cell::RefCell<Vec<Vec<u8>>>,
668 }
669
670 impl RawPackInstaller for RecordingInstaller {
671 fn install_raw_pack_from_reader_with_options<R>(
672 &self,
673 reader: &mut R,
674 _options: RawPackInstallOptions,
675 ) -> Result<RawPackInstallResult>
676 where
677 R: Read,
678 {
679 let mut pack_bytes = Vec::new();
680 reader.read_to_end(&mut pack_bytes)?;
681 self.packs.borrow_mut().push(pack_bytes.to_vec());
682 Ok(RawPackInstallResult {
683 object_ids: Vec::new(),
684 })
685 }
686 }
687
688 let installer = RecordingInstaller::default();
689 let response = UploadPackRawPackfileResponse {
690 acknowledgments: Vec::new(),
691 packfile: b"PACKcustom".to_vec(),
692 };
693 let encoded =
694 encode_upload_pack_raw_packfile_response(&response).expect("response should encode");
695 let mut reader = encoded.as_slice();
696
697 let result = install_upload_pack_raw_response_from_reader(
698 ObjectFormat::Sha1,
699 &mut reader, &installer, None)
700 .expect("test operation should succeed");
701
702 assert!(result.object_ids.is_empty());
703 assert_eq!(installer.packs.into_inner(), vec![b"PACKcustom".to_vec()]);
704 }
705
706 #[test]
707 fn raw_upload_pack_response_installs_into_in_memory_database() {
708 let format = ObjectFormat::Sha1;
709 let object = EncodedObject::new(ObjectType::Blob, b"in memory fetch pack\n".to_vec());
710 let oid = object
711 .object_id(format)
712 .expect("test operation should succeed");
713 let pack = PackFile::write_undeltified(std::slice::from_ref(&object), format)
714 .expect("test operation should succeed");
715 let response = UploadPackRawPackfileResponse {
716 acknowledgments: Vec::new(),
717 packfile: pack.pack,
718 };
719 let encoded =
720 encode_upload_pack_raw_packfile_response(&response).expect("response should encode");
721 let destination = ObjectDatabase::new(format);
722 let mut reader = encoded.as_slice();
723
724 let result =
725 install_upload_pack_raw_response_from_reader(format, &mut reader, &destination, None)
726 .expect("test operation should succeed");
727
728 assert_eq!(result.object_ids, vec![oid]);
729 assert_eq!(
730 destination
731 .read_object(&oid)
732 .expect("test operation should succeed")
733 .as_ref(),
734 &object
735 );
736 }
737
738 fn test_temp_root(prefix: &str) -> std::path::PathBuf {
739 let root = std::env::temp_dir().join(format!(
740 "{prefix}-{}-{}",
741 std::process::id(),
742 TEST_TEMP_COUNTER.fetch_add(1, Ordering::Relaxed)
743 ));
744 let _ = fs::remove_dir_all(&root);
745 root
746 }
747
748 fn assert_pack_install(
749 objects_dir: &Path,
750 db: &FileObjectDatabase,
751 oid: &sley_core::ObjectId,
752 object: &EncodedObject,
753 ) {
754 assert!(
755 !db.loose()
756 .object_path(oid)
757 .expect("test operation should succeed")
758 .exists()
759 );
760 let pack_dir = objects_dir.join("pack");
761 let packs = fs::read_dir(&pack_dir)
762 .expect("test operation should succeed")
763 .map(|entry| entry.expect("test operation should succeed").path())
764 .collect::<Vec<_>>();
765 assert!(
766 packs
767 .iter()
768 .any(|path| path.extension().and_then(|ext| ext.to_str()) == Some("pack"))
769 );
770 assert!(
771 packs
772 .iter()
773 .any(|path| path.extension().and_then(|ext| ext.to_str()) == Some("idx"))
774 );
775 assert!(db.contains(oid).expect("test operation should succeed"));
776 assert_eq!(
777 db.read_object(oid)
778 .expect("test operation should succeed")
779 .as_ref(),
780 object
781 );
782 }
783
784 fn assert_promisor_sidecar(objects_dir: &Path) {
785 let pack_dir = objects_dir.join("pack");
786 let promisors = fs::read_dir(&pack_dir)
787 .expect("test operation should succeed")
788 .map(|entry| entry.expect("test operation should succeed").path())
789 .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("promisor"))
790 .collect::<Vec<_>>();
791 assert_eq!(promisors.len(), 1);
792 assert_eq!(
793 fs::read(&promisors[0]).expect("test operation should succeed"),
794 b""
795 );
796 }
797}