1use std::{
5 io::{self, BufRead, Cursor, ErrorKind, Read},
6 path::PathBuf,
7 sync::{
8 Arc,
9 atomic::{AtomicUsize, Ordering},
10 },
11 thread::{self, JoinHandle},
12 time::Instant,
13};
14
15use axum::Error;
16use bytes::Bytes;
17use flate2::bufread::ZlibDecoder;
18use futures_util::{Stream, StreamExt};
19use threadpool::ThreadPool;
20use tokio::sync::mpsc::UnboundedSender;
21use uuid::Uuid;
22
23use crate::{
24 errors::GitError,
25 hash::{ObjectHash, get_hash_kind, set_hash_kind},
26 internal::{
27 metadata::{EntryMeta, MetaAttached},
28 object::types::ObjectType,
29 pack::{
30 DEFAULT_TMP_DIR, Pack,
31 cache::{_Cache, Caches},
32 cache_object::{CacheObject, CacheObjectInfo, MemSizeRecorder},
33 channel_reader::StreamBufReader,
34 entry::Entry,
35 utils,
36 waitlist::Waitlist,
37 wrapper::Wrapper,
38 },
39 },
40 utils::CountingReader,
41 zstdelta,
42};
43
44struct CrcCountingReader<'a, R> {
47 inner: R,
48 bytes_read: u64,
49 crc: &'a mut crc32fast::Hasher,
50}
51impl<R: Read> Read for CrcCountingReader<'_, R> {
52 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
53 let n = self.inner.read(buf)?;
54 self.bytes_read += n as u64;
55 self.crc.update(&buf[..n]);
56 Ok(n)
57 }
58}
59impl<R: BufRead> BufRead for CrcCountingReader<'_, R> {
60 fn fill_buf(&mut self) -> io::Result<&[u8]> {
61 self.inner.fill_buf()
62 }
63 fn consume(&mut self, amt: usize) {
64 let buf = self.inner.fill_buf().unwrap_or(&[]);
65 self.crc.update(&buf[..amt.min(buf.len())]);
66 self.bytes_read += amt as u64;
67 self.inner.consume(amt);
68 }
69}
70
71struct SharedParams {
73 pub pool: Arc<ThreadPool>,
74 pub waitlist: Arc<Waitlist>,
75 pub caches: Arc<Caches>,
76 pub cache_objs_mem_size: Arc<AtomicUsize>,
77 pub callback: Arc<dyn Fn(MetaAttached<Entry, EntryMeta>) + Sync + Send>,
78}
79
80impl Drop for Pack {
81 fn drop(&mut self) {
82 if self.clean_tmp {
83 self.abort_decode();
84 if let Err(e) = self.caches.remove_tmp_dir() {
85 tracing::warn!(error = %e, "failed to remove pack decode temp directory");
86 }
87 }
88 }
89}
90
91impl Pack {
92 fn abort_decode(&self) {
93 self.pool.join();
94 self.caches.shutdown();
95 }
96
97 pub fn new(
107 thread_num: Option<usize>,
108 mem_limit: Option<usize>,
109 temp_path: Option<PathBuf>,
110 clean_tmp: bool,
111 ) -> Self {
112 let mut temp_path = temp_path.unwrap_or(PathBuf::from(DEFAULT_TMP_DIR));
113 loop {
115 let sub_dir = Uuid::new_v4().to_string()[..8].to_string();
116 temp_path.push(sub_dir);
117 if !temp_path.exists() {
118 break;
119 }
120 temp_path.pop();
121 }
122 let thread_num = thread_num.unwrap_or_else(num_cpus::get);
123 let cache_mem_size = mem_limit.map(|mem_limit| {
124 ((mem_limit as u128) * 4 / 5) as usize
126 });
127 Pack {
128 number: 0,
129 signature: ObjectHash::default(),
130 objects: Vec::new(),
131 pool: Arc::new(ThreadPool::new(thread_num)),
132 waitlist: Arc::new(Waitlist::new()),
133 caches: Arc::new(Caches::new(cache_mem_size, temp_path, thread_num)),
134 mem_limit,
135 cache_objs_mem: Arc::new(AtomicUsize::default()),
136 clean_tmp,
137 }
138 }
139
140 pub fn check_header(pack: &mut impl BufRead) -> Result<(u32, Vec<u8>), GitError> {
164 let mut header_data = Vec::new();
166
167 let mut magic = [0; 4];
169 let result = pack.read_exact(&mut magic);
171 match result {
172 Ok(_) => {
173 header_data.extend_from_slice(&magic);
175
176 if magic != *b"PACK" {
178 return Err(GitError::InvalidPackHeader(format!(
180 "{},{},{},{}",
181 magic[0], magic[1], magic[2], magic[3]
182 )));
183 }
184 }
185 Err(e) => {
186 return Err(GitError::InvalidPackFile(format!(
188 "Error reading magic identifier: {e}"
189 )));
190 }
191 }
192
193 let mut version_bytes = [0; 4];
195 let result = pack.read_exact(&mut version_bytes); match result {
197 Ok(_) => {
198 header_data.extend_from_slice(&version_bytes);
200
201 let version = u32::from_be_bytes(version_bytes);
203 if version != 2 {
204 return Err(GitError::InvalidPackFile(format!(
206 "Version Number is {version}, not 2"
207 )));
208 }
209 }
210 Err(e) => {
211 return Err(GitError::InvalidPackFile(format!(
213 "Error reading version number: {e}"
214 )));
215 }
216 }
217
218 let mut object_num_bytes = [0; 4];
220 let result = pack.read_exact(&mut object_num_bytes);
222 match result {
223 Ok(_) => {
224 header_data.extend_from_slice(&object_num_bytes);
226 let object_num = u32::from_be_bytes(object_num_bytes);
228 Ok((object_num, header_data))
230 }
231 Err(e) => {
232 Err(GitError::InvalidPackFile(format!(
234 "Error reading object number: {e}"
235 )))
236 }
237 }
238 }
239
240 pub fn decompress_data(
252 pack: &mut (impl BufRead + Send),
253 expected_size: usize,
254 ) -> Result<(Vec<u8>, usize), GitError> {
255 let mut buf = Vec::with_capacity(expected_size);
257
258 let mut counting_reader = CountingReader::new(pack);
259 let mut deflate = ZlibDecoder::new(&mut counting_reader);
262 match deflate.read_to_end(&mut buf) {
264 Ok(_) => {
265 if buf.len() != expected_size {
267 Err(GitError::InvalidPackFile(format!(
268 "The object size {} does not match the expected size {}",
269 buf.len(),
270 expected_size
271 )))
272 } else {
273 let actual_input_bytes = counting_reader.bytes_read as usize;
275 Ok((buf, actual_input_bytes))
276 }
277 }
278 Err(e) => {
279 Err(GitError::InvalidPackFile(format!(
281 "Decompression error: {e}"
282 )))
283 }
284 }
285 }
286
287 pub fn decode_pack_object(
299 pack: &mut (impl BufRead + Send),
300 offset: &mut usize,
301 ) -> Result<Option<CacheObject>, GitError> {
302 let init_offset = *offset;
303 let mut hasher = crc32fast::Hasher::new();
304 let mut reader = CrcCountingReader {
305 inner: pack,
306 bytes_read: 0,
307 crc: &mut hasher,
308 };
309
310 let (type_bits, size) = match utils::read_type_and_varint_size(&mut reader, offset) {
313 Ok(result) => result,
314 Err(e) => {
315 return Err(GitError::InvalidPackFile(format!("Read error: {e}")));
318 }
319 };
320
321 let t = ObjectType::from_pack_type_u8(type_bits)?;
323
324 match t {
325 ObjectType::Commit | ObjectType::Tree | ObjectType::Blob | ObjectType::Tag => {
326 let (data, raw_size) = Pack::decompress_data(&mut reader, size)?;
327 *offset += raw_size;
328 let crc32 = hasher.finalize();
329 Ok(Some(CacheObject::new_for_undeltified(
330 t,
331 data,
332 init_offset,
333 crc32,
334 )))
335 }
336 ObjectType::OffsetDelta | ObjectType::OffsetZstdelta => {
337 let (delta_offset, bytes) =
338 utils::read_offset_encoding(&mut reader).map_err(|e| {
339 GitError::InvalidPackFile(format!("Read offset-delta base error: {e}"))
340 })?;
341 *offset += bytes;
342
343 let (data, raw_size) = Pack::decompress_data(&mut reader, size)?;
344 *offset += raw_size;
345
346 let delta_offset = usize::try_from(delta_offset).map_err(|_| {
347 GitError::InvalidObjectInfo("Invalid OffsetDelta offset".to_string())
348 })?;
349 let base_offset = init_offset.checked_sub(delta_offset).ok_or_else(|| {
350 GitError::InvalidObjectInfo("Invalid OffsetDelta offset".to_string())
351 })?;
352
353 let mut reader = Cursor::new(&data);
354 let (_, final_size) = utils::read_delta_object_size(&mut reader)?;
355
356 let obj_info = match t {
357 ObjectType::OffsetDelta => {
358 CacheObjectInfo::OffsetDelta(base_offset, final_size)
359 }
360 ObjectType::OffsetZstdelta => {
361 CacheObjectInfo::OffsetZstdelta(base_offset, final_size)
362 }
363 _ => unreachable!(),
364 };
365 let crc32 = hasher.finalize();
366 Ok(Some(CacheObject {
367 info: obj_info,
368 offset: init_offset,
369 crc32,
370 data_decompressed: data,
371 mem_recorder: None,
372 is_delta_in_pack: true,
373 }))
374 }
375 ObjectType::HashDelta => {
376 let ref_sha = ObjectHash::from_stream(&mut reader).map_err(|e| {
378 GitError::InvalidPackFile(format!("Read hash-delta base hash error: {e}"))
379 })?;
380 *offset += get_hash_kind().size();
382
383 let (data, raw_size) = Pack::decompress_data(&mut reader, size)?;
384 *offset += raw_size;
385
386 let mut reader = Cursor::new(&data);
387 let (_, final_size) = utils::read_delta_object_size(&mut reader)?;
388
389 let crc32 = hasher.finalize();
390
391 Ok(Some(CacheObject {
392 info: CacheObjectInfo::HashDelta(ref_sha, final_size),
393 offset: init_offset,
394 crc32,
395 data_decompressed: data,
396 mem_recorder: None,
397 is_delta_in_pack: true,
398 }))
399 }
400 other => Err(GitError::InvalidPackFile(format!(
404 "AI object type `{other}` cannot appear in a pack file"
405 ))),
406 }
407 }
408
409 pub fn decode<F, C>(
416 &mut self,
417 pack: &mut (impl BufRead + Send),
418 callback: F,
419 pack_id_callback: Option<C>,
420 ) -> Result<(), GitError>
421 where
422 F: Fn(MetaAttached<Entry, EntryMeta>) + Sync + Send + 'static,
423 C: FnOnce(ObjectHash) + Send + 'static,
424 {
425 let time = Instant::now();
426 let mut last_update_time = time.elapsed().as_millis();
427 let log_info = |_i: usize, pack: &Pack| {
428 tracing::info!(
429 "time {:.2} s \t decode: {:?} \t dec-num: {} \t cah-num: {} \t Objs: {} MB \t CacheUsed: {} MB",
430 time.elapsed().as_millis() as f64 / 1000.0,
431 _i,
432 pack.pool.queued_count(),
433 pack.caches.queued_tasks(),
434 pack.cache_objs_mem_used() / 1024 / 1024,
435 pack.caches.memory_used() / 1024 / 1024
436 );
437 };
438 let callback = Arc::new(callback);
439
440 let caches = self.caches.clone();
441 let mut reader = Wrapper::new(io::BufReader::new(pack));
442
443 let result = Pack::check_header(&mut reader);
444 match result {
445 Ok((object_num, _)) => {
446 self.number = object_num as usize;
447 }
448 Err(e) => {
449 return Err(e);
450 }
451 }
452 tracing::info!("The pack file has {} objects", self.number);
453 let mut offset: usize = 12;
454 let mut i = 0;
455 while i < self.number {
456 if i % 1000 == 0 {
458 let time_now = time.elapsed().as_millis();
459 if time_now - last_update_time > 1000 {
460 log_info(i, self);
461 last_update_time = time_now;
462 }
463 }
464 while self.pool.queued_count() > 2000
467 || self
468 .mem_limit
469 .map(|limit| self.memory_used() > limit)
470 .unwrap_or(false)
471 {
472 thread::yield_now();
473 }
474 let r: Result<Option<CacheObject>, GitError> =
475 Pack::decode_pack_object(&mut reader, &mut offset);
476 match r {
477 Ok(Some(mut obj)) => {
478 obj.set_mem_recorder(self.cache_objs_mem.clone());
479 obj.record_mem_size();
480
481 let params = Arc::new(SharedParams {
483 pool: self.pool.clone(),
484 waitlist: self.waitlist.clone(),
485 caches: self.caches.clone(),
486 cache_objs_mem_size: self.cache_objs_mem.clone(),
487 callback: callback.clone(),
488 });
489
490 let caches = caches.clone();
491 let waitlist = self.waitlist.clone();
492 let kind = get_hash_kind();
493 self.pool.execute(move || {
494 set_hash_kind(kind);
495 match obj.info {
496 CacheObjectInfo::BaseObject(_, _) => {
497 Self::cache_obj_and_process_waitlist(params, obj);
498 }
499 CacheObjectInfo::OffsetDelta(base_offset, _)
500 | CacheObjectInfo::OffsetZstdelta(base_offset, _) => {
501 if let Some(base_obj) = caches.get_by_offset(base_offset) {
502 Self::process_delta(params, obj, base_obj);
503 } else {
504 waitlist.insert_offset(base_offset, obj);
507 if let Some(base_obj) = caches.get_by_offset(base_offset) {
509 Self::process_waitlist(params, base_obj);
510 }
511 }
512 }
513 CacheObjectInfo::HashDelta(base_ref, _) => {
514 if let Some(base_obj) = caches.get_by_hash(base_ref) {
515 Self::process_delta(params, obj, base_obj);
516 } else {
517 waitlist.insert_ref(base_ref, obj);
518 if let Some(base_obj) = caches.get_by_hash(base_ref) {
519 Self::process_waitlist(params, base_obj);
520 }
521 }
522 }
523 }
524 });
525 }
526 Ok(None) => {}
527 Err(e) => {
528 self.abort_decode();
529 return Err(e);
530 }
531 }
532 i += 1;
533 }
534 log_info(i, self);
535 let render_hash = reader.final_hash();
536 self.signature = match ObjectHash::from_stream(&mut reader) {
537 Ok(signature) => signature,
538 Err(e) => {
539 self.abort_decode();
540 return Err(GitError::InvalidPackFile(format!(
541 "Error reading pack trailer hash: {e}"
542 )));
543 }
544 };
545
546 if render_hash != self.signature {
547 self.abort_decode();
548 return Err(GitError::InvalidPackFile(format!(
549 "The pack file hash {} does not match the trailer hash {}",
550 render_hash, self.signature
551 )));
552 }
553
554 let end = utils::is_eof(&mut reader);
555 if !end {
556 self.abort_decode();
557 return Err(GitError::InvalidPackFile(
558 "The pack file is not at the end".to_string(),
559 ));
560 }
561
562 self.pool.join(); if let Some(pack_callback) = pack_id_callback {
566 pack_callback(self.signature);
567 }
568 assert_eq!(self.waitlist.map_offset.len(), 0);
571 assert_eq!(self.waitlist.map_ref.len(), 0);
572 assert!(self.number >= caches.total_inserted());
574 tracing::info!(
575 "The pack file has been decoded successfully, takes: [ {:?} ]",
576 time.elapsed()
577 );
578 self.caches.clear(); assert_eq!(self.cache_objs_mem_used(), 0); Ok(())
587 }
588
589 pub fn decode_async(
592 mut self,
593 mut pack: impl BufRead + Send + 'static,
594 sender: UnboundedSender<Entry>,
595 ) -> JoinHandle<Pack> {
596 let kind = get_hash_kind();
597 thread::spawn(move || {
598 set_hash_kind(kind);
599 self.decode(
600 &mut pack,
601 move |entry| {
602 if let Err(e) = sender.send(entry.inner) {
603 eprintln!("Channel full, failed to send entry: {e:?}");
604 }
605 },
606 None::<fn(ObjectHash)>,
607 )
608 .unwrap();
609 self
610 })
611 }
612
613 pub async fn decode_stream(
615 mut self,
616 mut stream: impl Stream<Item = Result<Bytes, Error>> + Unpin + Send + 'static,
617 sender: UnboundedSender<MetaAttached<Entry, EntryMeta>>,
618 pack_hash_send: Option<UnboundedSender<ObjectHash>>,
619 ) -> Self {
620 let kind = get_hash_kind();
621 let (tx, rx) = std::sync::mpsc::channel();
622 let mut reader = StreamBufReader::new(rx);
623 tokio::spawn(async move {
624 while let Some(chunk) = stream.next().await {
625 let data = chunk.unwrap().to_vec();
626 if let Err(e) = tx.send(data) {
627 eprintln!("Sending Error: {e:?}");
628 break;
629 }
630 }
631 });
632 tokio::task::spawn_blocking(move || {
635 set_hash_kind(kind);
636 self.decode(
637 &mut reader,
638 move |entry: MetaAttached<Entry, EntryMeta>| {
639 if let Err(e) = sender.send(entry) {
641 eprintln!("unbound channel Sending Error: {e:?}");
642 }
643 },
644 Some(move |pack_id: ObjectHash| {
645 if let Some(pack_id_send) = pack_hash_send
646 && let Err(e) = pack_id_send.send(pack_id)
647 {
648 eprintln!("unbound channel Sending Error: {e:?}");
649 }
650 }),
651 )
652 .unwrap();
653 self
654 })
655 .await
656 .unwrap()
657 }
658
659 fn memory_used(&self) -> usize {
661 self.cache_objs_mem_used() + self.caches.memory_used_index()
662 }
663
664 fn cache_objs_mem_used(&self) -> usize {
666 self.cache_objs_mem.load(Ordering::Acquire)
667 }
668
669 fn process_delta(
672 shared_params: Arc<SharedParams>,
673 delta_obj: CacheObject,
674 base_obj: Arc<CacheObject>,
675 ) {
676 shared_params.pool.clone().execute(move || {
677 let mut new_obj = match delta_obj.info {
678 CacheObjectInfo::OffsetDelta(_, _) | CacheObjectInfo::HashDelta(_, _) => {
679 Pack::rebuild_delta(delta_obj, base_obj)
680 }
681 CacheObjectInfo::OffsetZstdelta(_, _) => {
682 Pack::rebuild_zstdelta(delta_obj, base_obj)
683 }
684 _ => unreachable!(),
685 };
686
687 new_obj.set_mem_recorder(shared_params.cache_objs_mem_size.clone());
688 new_obj.record_mem_size();
689 Self::cache_obj_and_process_waitlist(shared_params, new_obj); });
691 }
692
693 fn cache_obj_and_process_waitlist(shared_params: Arc<SharedParams>, new_obj: CacheObject) {
695 (shared_params.callback)(new_obj.to_entry_metadata());
696 let new_obj = shared_params.caches.insert(
697 new_obj.offset,
698 new_obj.base_object_hash().unwrap(),
699 new_obj,
700 );
701 Self::process_waitlist(shared_params, new_obj);
702 }
703
704 fn process_waitlist(shared_params: Arc<SharedParams>, base_obj: Arc<CacheObject>) {
705 let wait_objs = shared_params
706 .waitlist
707 .take(base_obj.offset, base_obj.base_object_hash().unwrap());
708 for obj in wait_objs {
709 Self::process_delta(shared_params.clone(), obj, base_obj.clone());
711 }
712 }
713
714 pub fn rebuild_delta(delta_obj: CacheObject, base_obj: Arc<CacheObject>) -> CacheObject {
717 const COPY_INSTRUCTION_FLAG: u8 = 1 << 7;
718 const COPY_OFFSET_BYTES: u8 = 4;
719 const COPY_SIZE_BYTES: u8 = 3;
720 const COPY_ZERO_SIZE: usize = 0x10000;
721
722 let mut stream = Cursor::new(&delta_obj.data_decompressed);
723
724 let (base_size, result_size) = utils::read_delta_object_size(&mut stream).unwrap();
727
728 let base_info = &base_obj.data_decompressed;
730 assert_eq!(base_info.len(), base_size, "Base object size mismatch");
731
732 let mut result = Vec::with_capacity(result_size);
733
734 loop {
735 let instruction = match utils::read_bytes(&mut stream) {
737 Ok([instruction]) => instruction,
738 Err(err) if err.kind() == ErrorKind::UnexpectedEof => break,
739 Err(err) => {
740 panic!(
741 "{}",
742 GitError::DeltaObjectError(format!("Wrong instruction in delta :{err}"))
743 );
744 }
745 };
746
747 if instruction & COPY_INSTRUCTION_FLAG == 0 {
748 if instruction == 0 {
750 panic!(
752 "{}",
753 GitError::DeltaObjectError(String::from("Invalid data instruction"))
754 );
755 }
756
757 let mut data = vec![0; instruction as usize];
759 stream.read_exact(&mut data).unwrap();
760 result.extend_from_slice(&data);
761 } else {
762 let mut nonzero_bytes = instruction;
767 let offset =
768 utils::read_partial_int(&mut stream, COPY_OFFSET_BYTES, &mut nonzero_bytes)
769 .unwrap();
770 let mut size =
771 utils::read_partial_int(&mut stream, COPY_SIZE_BYTES, &mut nonzero_bytes)
772 .unwrap();
773 if size == 0 {
774 size = COPY_ZERO_SIZE;
776 }
777 let base_data = base_info.get(offset..(offset + size)).ok_or_else(|| {
779 GitError::DeltaObjectError("Invalid copy instruction".to_string())
780 });
781
782 match base_data {
783 Ok(data) => result.extend_from_slice(data),
784 Err(e) => panic!("{}", e),
785 }
786 }
787 }
788 assert_eq!(result_size, result.len(), "Result size mismatch");
789
790 let hash = utils::calculate_object_hash(base_obj.object_type(), &result);
791 CacheObject {
793 info: CacheObjectInfo::BaseObject(base_obj.object_type(), hash),
794 offset: delta_obj.offset,
795 crc32: delta_obj.crc32,
796 data_decompressed: result,
797 mem_recorder: None,
798 is_delta_in_pack: delta_obj.is_delta_in_pack,
799 } }
802 pub fn rebuild_zstdelta(delta_obj: CacheObject, base_obj: Arc<CacheObject>) -> CacheObject {
803 let result = zstdelta::apply(&base_obj.data_decompressed, &delta_obj.data_decompressed)
804 .expect("Failed to apply zstdelta");
805 let hash = utils::calculate_object_hash(base_obj.object_type(), &result);
806 CacheObject {
807 info: CacheObjectInfo::BaseObject(base_obj.object_type(), hash),
808 offset: delta_obj.offset,
809 crc32: delta_obj.crc32,
810 data_decompressed: result,
811 mem_recorder: None,
812 is_delta_in_pack: delta_obj.is_delta_in_pack,
813 } }
816}
817
818#[cfg(test)]
819mod tests {
820 use std::{
821 fs,
822 io::{BufReader, Cursor, prelude::*},
823 path::PathBuf,
824 sync::{
825 Arc,
826 atomic::{AtomicUsize, Ordering},
827 },
828 };
829
830 use flate2::{Compression, write::ZlibEncoder};
831 use futures_util::TryStreamExt;
832 use tokio_util::io::ReaderStream;
833
834 use crate::{
835 hash::{HashKind, ObjectHash, set_hash_kind_for_test},
836 internal::pack::{Pack, test_pack_download::download_pack_file, tests::init_logger},
837 };
838
839 #[tokio::test]
840 async fn test_pack_check_header() {
841 let (source, _guard) = download_pack_file("medium-sha1.pack");
842
843 let f = fs::File::open(source).unwrap();
844 let mut buf_reader = BufReader::new(f);
845 let (object_num, _) = Pack::check_header(&mut buf_reader).unwrap();
846
847 assert_eq!(object_num, 35031);
848 }
849
850 #[test]
851 fn test_decompress_data() {
852 let data = b"Hello, world!"; let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
854 encoder.write_all(data).unwrap();
855 let compressed_data = encoder.finish().unwrap();
856 let compressed_size = compressed_data.len();
857
858 let mut cursor: Cursor<Vec<u8>> = Cursor::new(compressed_data);
860 let expected_size = data.len();
861
862 let result = Pack::decompress_data(&mut cursor, expected_size);
864 match result {
865 Ok((decompressed_data, bytes_read)) => {
866 assert_eq!(bytes_read, compressed_size);
867 assert_eq!(decompressed_data, data);
868 }
869 Err(e) => panic!("Decompression failed: {e:?}"),
870 }
871 }
872
873 #[test]
874 fn test_pack_decode_truncated_pack_returns_err_without_panic() {
875 let _guard = set_hash_kind_for_test(HashKind::Sha1);
876 let (source, _dl_guard) = download_pack_file("small-sha1.pack");
877 let mut bytes = fs::read(source).unwrap();
878 bytes.truncate(bytes.len() - 1);
879
880 let tmp_dir = tempfile::tempdir().unwrap();
881 let tmp_path = tmp_dir.path().to_path_buf();
882 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
883 let mut buffered = BufReader::new(Cursor::new(bytes));
884 let mut pack = Pack::new(Some(2), Some(1024 * 1024), Some(tmp_path), true);
885 pack.decode(&mut buffered, |_| {}, None::<fn(ObjectHash)>)
886 }));
887
888 assert!(result.is_ok(), "truncated pack decode should not panic");
889 assert!(
890 matches!(
891 result.unwrap(),
892 Err(crate::errors::GitError::InvalidPackFile(_))
893 | Err(crate::errors::GitError::IOError(_))
894 ),
895 "truncated pack decode should return a pack error"
896 );
897 }
898
899 #[test]
900 #[cfg(target_pointer_width = "32")]
901 fn test_pack_new_mem_limit_no_overflow_32bit() {
902 let mem_limit = 1_200_000_000usize;
906 let tmp = PathBuf::from("/tmp/.cache_temp");
907 let result = std::panic::catch_unwind(|| {
908 let _p = Pack::new(Some(1), Some(mem_limit), Some(tmp), true);
909 });
910 assert!(result.is_ok(), "Pack::new should not panic on 32-bit");
911 }
912
913 fn run_decode_no_delta(filename: &str, kind: HashKind) {
915 let _guard = set_hash_kind_for_test(kind);
916 let (source, _dl_guard) = download_pack_file(filename);
917
918 let tmp = PathBuf::from("/tmp/.cache_temp");
919
920 let f = fs::File::open(source).unwrap();
921 let mut buffered = BufReader::new(f);
922 let mut p = Pack::new(None, Some(1024 * 1024 * 20), Some(tmp), true);
923 p.decode(&mut buffered, |_| {}, None::<fn(ObjectHash)>)
924 .unwrap();
925 }
926 #[test]
927 fn test_pack_decode_without_delta() {
928 run_decode_no_delta("small-sha1.pack", HashKind::Sha1);
929 run_decode_no_delta("small-sha256.pack", HashKind::Sha256);
930 }
931
932 fn run_decode_with_ref_delta(filename: &str, kind: HashKind) {
934 let _guard = set_hash_kind_for_test(kind);
935 init_logger();
936
937 let (source, _dl_guard) = download_pack_file(filename);
938
939 let tmp = PathBuf::from("/tmp/.cache_temp");
940
941 let f = fs::File::open(source).unwrap();
942 let mut buffered = BufReader::new(f);
943 let mut p = Pack::new(None, Some(1024 * 1024 * 20), Some(tmp), true);
944 p.decode(&mut buffered, |_| {}, None::<fn(ObjectHash)>)
945 .unwrap();
946 }
947 #[test]
948 fn test_pack_decode_with_ref_delta() {
949 run_decode_with_ref_delta("ref-delta-sha1.pack", HashKind::Sha1);
950 run_decode_with_ref_delta("ref-delta-sha256.pack", HashKind::Sha256);
951 }
952
953 fn run_decode_no_mem_limit(filename: &str, kind: HashKind) {
955 let _guard = set_hash_kind_for_test(kind);
956 let (source, _dl_guard) = download_pack_file(filename);
957
958 let tmp = PathBuf::from("/tmp/.cache_temp");
959
960 let f = fs::File::open(source).unwrap();
961 let mut buffered = BufReader::new(f);
962 let mut p = Pack::new(None, None, Some(tmp), true);
963 p.decode(&mut buffered, |_| {}, None::<fn(ObjectHash)>)
964 .unwrap();
965 }
966 #[test]
967 fn test_pack_decode_no_mem_limit() {
968 run_decode_no_mem_limit("small-sha1.pack", HashKind::Sha1);
969 run_decode_no_mem_limit("small-sha256.pack", HashKind::Sha256);
970 }
971
972 async fn run_decode_large_with_delta(filename: &str, kind: HashKind) {
974 let _guard = set_hash_kind_for_test(kind);
975 init_logger();
976 let (source, _dl_guard) = download_pack_file(filename);
977
978 let tmp = PathBuf::from("/tmp/.cache_temp");
979
980 let f = fs::File::open(source).unwrap();
981 let mut buffered = BufReader::new(f);
982 let mut p = Pack::new(
983 Some(4),
984 Some(1024 * 1024 * 100), Some(tmp.clone()),
986 true,
987 );
988 let rt = p.decode(
989 &mut buffered,
990 |_obj| {
991 },
993 None::<fn(ObjectHash)>,
994 );
995 if let Err(e) = rt {
996 fs::remove_dir_all(tmp).unwrap();
997 panic!("Error: {e:?}");
998 }
999 }
1000 #[tokio::test]
1001 async fn test_pack_decode_with_large_file_with_delta_without_ref() {
1002 run_decode_large_with_delta("medium-sha1.pack", HashKind::Sha1).await;
1003 run_decode_large_with_delta("medium-sha256.pack", HashKind::Sha256).await;
1004 } async fn run_decode_large_stream(filename: &str, kind: HashKind) {
1008 let _guard = set_hash_kind_for_test(kind);
1009 init_logger();
1010 let (source, _dl_guard) = download_pack_file(filename);
1011
1012 let tmp = PathBuf::from("/tmp/.cache_temp");
1013 let f = tokio::fs::File::open(source).await.unwrap();
1014 let stream = ReaderStream::new(f).map_err(axum::Error::new);
1015 let p = Pack::new(Some(4), Some(1024 * 1024 * 100), Some(tmp.clone()), true);
1016
1017 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
1018 let handle = tokio::spawn(async move { p.decode_stream(stream, tx, None).await });
1019 let count = Arc::new(AtomicUsize::new(0));
1020 let count_c = count.clone();
1021 let consume = tokio::spawn(async move {
1023 let mut cnt = 0;
1024 while let Some(_entry) = rx.recv().await {
1025 cnt += 1;
1026 }
1027 tracing::info!("Received: {}", cnt);
1028 count_c.store(cnt, Ordering::Release);
1029 });
1030 let p = handle.await.unwrap();
1031 consume.await.unwrap();
1032 assert_eq!(count.load(Ordering::Acquire), p.number);
1033 assert_eq!(p.number, 35031);
1034 }
1035 #[tokio::test]
1036 async fn test_decode_large_file_stream() {
1037 run_decode_large_stream("medium-sha1.pack", HashKind::Sha1).await;
1038 run_decode_large_stream("medium-sha256.pack", HashKind::Sha256).await;
1039 }
1040
1041 async fn run_decode_large_file_async(filename: &str, kind: HashKind) {
1043 let _guard = set_hash_kind_for_test(kind);
1044 let (source, _dl_guard) = download_pack_file(filename);
1045
1046 let tmp = PathBuf::from("/tmp/.cache_temp");
1047 let f = fs::File::open(source).unwrap();
1048 let buffered = BufReader::new(f);
1049 let p = Pack::new(Some(4), Some(1024 * 1024 * 100), Some(tmp.clone()), true);
1050
1051 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
1052 let handle = p.decode_async(buffered, tx); let mut cnt = 0;
1054 while let Some(_entry) = rx.recv().await {
1055 cnt += 1; }
1057 let p = handle.join().unwrap();
1058 assert_eq!(cnt, p.number);
1059 }
1060 #[tokio::test]
1061 async fn test_decode_large_file_async() {
1062 run_decode_large_file_async("medium-sha1.pack", HashKind::Sha1).await;
1063 run_decode_large_file_async("medium-sha256.pack", HashKind::Sha256).await;
1064 }
1065
1066 fn run_decode_with_delta_no_ref(filename: &str, kind: HashKind) {
1068 let _guard = set_hash_kind_for_test(kind);
1069 let (source, _dl_guard) = download_pack_file(filename);
1070
1071 let tmp = PathBuf::from("/tmp/.cache_temp");
1072
1073 let f = fs::File::open(source).unwrap();
1074 let mut buffered = BufReader::new(f);
1075 let mut p = Pack::new(None, Some(1024 * 1024 * 20), Some(tmp), true);
1076 p.decode(&mut buffered, |_| {}, None::<fn(ObjectHash)>)
1077 .unwrap();
1078 }
1079 #[test]
1080 fn test_pack_decode_with_delta_without_ref() {
1081 run_decode_with_delta_no_ref("medium-sha1.pack", HashKind::Sha1);
1082 run_decode_with_delta_no_ref("medium-sha256.pack", HashKind::Sha256);
1083 }
1084
1085 #[test] fn test_pack_decode_multi_task_with_large_file_with_delta_without_ref() {
1087 let rt = tokio::runtime::Builder::new_current_thread()
1088 .enable_all()
1089 .build()
1090 .unwrap();
1091 rt.block_on(async move {
1092 for (kind, filename) in [
1094 (HashKind::Sha1, "medium-sha1.pack"),
1095 (HashKind::Sha256, "medium-sha256.pack"),
1096 ] {
1097 let f1 = run_decode_large_with_delta(filename, kind);
1098 let f2 = run_decode_large_with_delta(filename, kind);
1099 let _ = futures::future::join(f1, f2).await;
1100 }
1101 });
1102 }
1103}