1use flate2::Compression;
2use flate2::read::ZlibDecoder;
3use flate2::write::ZlibEncoder;
4use flate2::{Decompress, FlushDecompress};
5use parking_lot::RwLock;
6use std::sync::Mutex;
7use sley_core::{GitError, MissingObjectContext, ObjectFormat, ObjectId, Result};
8use sley_formats::{Bundle, BundleReference};
9use sley_object::{
10 Commit, EncodedObject, ObjectType, Tag, TreeEntries, parse_framed_object,
11 tree_entry_object_type,
12};
13use sley_pack::{
14 MultiPackIndex, MultiPackIndexOidLookup, PackBitmapIndex, PackBitmapWriter, PackFile,
15 PackIndex, PackIndexByteSource, PackIndexEntry, PackIndexViewData, PackInput,
16 PackReverseIndex, PackStreamIndexBuild, PackWrite, PackWriteOptions, PackWriteSummary,
17};
18use std::collections::{HashMap, HashSet};
19use std::io::{Read, Write};
20use std::path::{Path, PathBuf};
21use std::sync::atomic::{AtomicU64, Ordering};
22use std::sync::{Arc, OnceLock};
23use std::{env, fs};
24
25use crate::{
26 grafted_parents, implied_empty_tree_object, unique_temp_path, with_missing_object_context,
27 ObjectReader, ObjectWriter,
28};
29
30use crate::pack::{FileObjectDatabase, ObjectDatabase};
31use crate::loose::LooseObjectStore;
32use crate::repack::pack_index_entries_match_writer;
33
34pub struct BundleUnbundleResult {
35 pub written_objects: Vec<ObjectId>,
36 pub references: Vec<BundleReference>,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct PackUnpackResult {
41 pub written_objects: Vec<ObjectId>,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct PackInstallResult {
46 pub pack_name: String,
47 pub pack_path: PathBuf,
48 pub index_path: PathBuf,
49 pub promisor_path: Option<PathBuf>,
50 pub object_ids: Vec<ObjectId>,
51}
52
53#[derive(Debug)]
54pub struct RawPackStreamingInstall {
55 format: ObjectFormat,
56 expected_pack_id: ObjectId,
57 expected_pack_size: u64,
58 options: RawPackInstallOptions,
59 pack_dir: PathBuf,
60 pack_name: String,
61 pack_path: PathBuf,
62 index_path: PathBuf,
63 temp_pack_path: PathBuf,
64 file: Option<fs::File>,
65 written: u64,
66 finished: bool,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct RawPackInstallResult {
71 pub object_ids: Vec<ObjectId>,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct RawPackIndexResult {
76 pub pack_id: ObjectId,
77 pub index: Vec<u8>,
78 pub objects: Vec<RawPackIndexedObject>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct RawPackIndexedObject {
83 pub oid: ObjectId,
84 pub object_type: ObjectType,
85 pub size: u64,
86 pub offset: u64,
87}
88
89struct PackInstallTeeReader<'a, R, W> {
90 reader: &'a mut R,
91 writer: &'a mut W,
92 max_input_size: Option<u64>,
93 written: u64,
94}
95
96impl<R, W> Read for PackInstallTeeReader<'_, R, W>
97where
98 R: Read,
99 W: Write,
100{
101 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
102 let len = self.reader.read(buf)?;
103 if len > 0 {
104 let next_written = self.written.checked_add(len as u64).ok_or_else(|| {
105 std::io::Error::new(std::io::ErrorKind::InvalidData, "pack size overflow")
106 })?;
107 if let Some(limit) = self.max_input_size
108 && next_written > limit
109 {
110 return Err(std::io::Error::new(
111 std::io::ErrorKind::InvalidData,
112 format!("pack exceeds maximum allowed size ({limit})"),
113 ));
114 }
115 self.writer.write_all(&buf[..len])?;
116 self.written = next_written;
117 }
118 Ok(len)
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct ReachablePackFile {
124 pub pack_path: PathBuf,
125 pub pack_size: u64,
126 pub checksum: ObjectId,
127 pub object_count: usize,
128 pub delta_count: u32,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct ReachablePackWriteSummary {
133 pub index: Vec<u8>,
134 pub checksum: ObjectId,
135 pub object_count: usize,
136 pub delta_count: u32,
137 pub pack_size: u64,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
141pub struct RawPackInstallOptions {
142 pub promisor: bool,
143 pub max_input_size: Option<u64>,
146}
147
148pub trait RawPackInstaller {
149 fn install_raw_pack_from_reader_with_options<R>(
150 &self,
151 reader: &mut R,
152 options: RawPackInstallOptions,
153 ) -> Result<RawPackInstallResult>
154 where
155 R: Read;
156
157 fn install_raw_pack_from_reader<R>(&self, reader: &mut R) -> Result<RawPackInstallResult>
158 where
159 R: Read,
160 {
161 self.install_raw_pack_from_reader_with_options(reader, RawPackInstallOptions::default())
162 }
163}
164
165#[cfg(test)]
166pub(crate) const REACHABLE_PACK_STREAMING_MIN_OBJECTS: usize = 32;
167#[cfg(not(test))]
168pub(crate) const REACHABLE_PACK_STREAMING_MIN_OBJECTS: usize = 4096;
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub enum ObjectPrefixResolution {
172 Missing,
173 Unique(ObjectId),
174 Ambiguous(Vec<ObjectId>),
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct ObjectStorageInfo {
179 pub disk_size: u64,
180 pub deltabase: ObjectId,
181}
182
183impl RawPackInstaller for FileObjectDatabase {
184 fn install_raw_pack_from_reader_with_options<R>(
185 &self,
186 reader: &mut R,
187 options: RawPackInstallOptions,
188 ) -> Result<RawPackInstallResult>
189 where
190 R: Read,
191 {
192 let result =
193 FileObjectDatabase::install_raw_pack_from_reader_with_options(self, reader, options)?;
194 Ok(RawPackInstallResult {
195 object_ids: result.object_ids,
196 })
197 }
198}
199
200impl RawPackInstaller for ObjectDatabase {
201 fn install_raw_pack_from_reader_with_options<R>(
202 &self,
203 reader: &mut R,
204 options: RawPackInstallOptions,
205 ) -> Result<RawPackInstallResult>
206 where
207 R: Read,
208 {
209 let mut pack_bytes = Vec::new();
210 match options.max_input_size {
211 Some(limit) => {
212 reader
213 .take(limit.saturating_add(1))
214 .read_to_end(&mut pack_bytes)?;
215 if pack_bytes.len() as u64 > limit {
216 return Err(GitError::InvalidFormat(format!(
217 "pack exceeds maximum allowed size ({limit})"
218 )));
219 }
220 }
221 None => {
222 reader.read_to_end(&mut pack_bytes)?;
223 }
224 }
225 let result = unpack_packfile_objects(&pack_bytes, self.format, self)?;
226 Ok(RawPackInstallResult {
227 object_ids: result.written_objects,
228 })
229 }
230}
231
232impl RawPackStreamingInstall {
233 pub fn bytes_written(&self) -> u64 {
234 self.written
235 }
236
237 pub fn pack_path(&self) -> &Path {
238 &self.pack_path
239 }
240
241 pub fn index_path(&self) -> &Path {
242 &self.index_path
243 }
244
245 pub fn finish(mut self) -> Result<PackInstallResult> {
246 let result = (|| -> Result<PackInstallResult> {
247 let mut file = self.file.take().ok_or_else(|| {
248 GitError::InvalidFormat("raw pack stream already finished".into())
249 })?;
250 file.flush()?;
251 file.sync_all()?;
252 drop(file);
253
254 if self.written != self.expected_pack_size {
255 return Err(GitError::InvalidFormat(format!(
256 "raw pack stream length mismatch: expected {}, got {}",
257 self.expected_pack_size, self.written
258 )));
259 }
260
261 let built = PackIndex::write_v2_for_pack_path(&self.temp_pack_path, self.format)?;
262 if built.pack_checksum != self.expected_pack_id {
263 return Err(GitError::InvalidFormat(format!(
264 "raw pack stream checksum mismatch: expected {}, got {}",
265 self.expected_pack_id, built.pack_checksum
266 )));
267 }
268
269 match fs::rename(&self.temp_pack_path, &self.pack_path) {
270 Ok(()) => {}
271 Err(_) if self.pack_path.exists() => {
272 let _ = fs::remove_file(&self.temp_pack_path);
273 }
274 Err(err) => return Err(GitError::Io(err.to_string())),
275 }
276 write_pack_component(&self.index_path, &built.index)?;
277 let promisor_path = write_promisor_pack_sidecar(
278 &self.pack_dir,
279 &self.pack_name,
280 self.options.promisor,
281 )?;
282 Ok(PackInstallResult {
283 pack_name: self.pack_name.clone(),
284 pack_path: self.pack_path.clone(),
285 index_path: self.index_path.clone(),
286 promisor_path,
287 object_ids: built.entries.iter().map(|entry| entry.oid).collect(),
288 })
289 })();
290
291 if result.is_ok() {
292 self.finished = true;
293 } else {
294 let _ = fs::remove_file(&self.temp_pack_path);
295 }
296 result
297 }
298}
299
300impl Write for RawPackStreamingInstall {
301 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
302 let next_written = self.written.checked_add(buf.len() as u64).ok_or_else(|| {
303 std::io::Error::new(std::io::ErrorKind::InvalidData, "pack size overflow")
304 })?;
305 if next_written > self.expected_pack_size {
306 return Err(std::io::Error::new(
307 std::io::ErrorKind::InvalidData,
308 format!(
309 "raw pack stream exceeds expected size {}; got at least {}",
310 self.expected_pack_size, next_written
311 ),
312 ));
313 }
314 let file = self.file.as_mut().ok_or_else(|| {
315 std::io::Error::new(
316 std::io::ErrorKind::BrokenPipe,
317 "raw pack stream already finished",
318 )
319 })?;
320 let written = file.write(buf)?;
321 self.written = self.written.checked_add(written as u64).ok_or_else(|| {
322 std::io::Error::new(std::io::ErrorKind::InvalidData, "pack size overflow")
323 })?;
324 Ok(written)
325 }
326
327 fn flush(&mut self) -> std::io::Result<()> {
328 match self.file.as_mut() {
329 Some(file) => file.flush(),
330 None => Ok(()),
331 }
332 }
333}
334
335impl Drop for RawPackStreamingInstall {
336 fn drop(&mut self) {
337 if !self.finished {
338 let _ = self.file.take();
339 let _ = fs::remove_file(&self.temp_pack_path);
340 }
341 }
342}
343
344pub fn verify_bundle_prerequisites<R: ObjectReader>(bundle: &Bundle, reader: &R) -> Result<()> {
345 let mut missing = Vec::new();
346 for prerequisite in &bundle.prerequisites {
347 match reader.read_object(&prerequisite.oid) {
348 Ok(object) => {
349 let actual = object.object_id(bundle.format)?;
350 if actual != prerequisite.oid {
351 return Err(GitError::InvalidObject(format!(
352 "bundle prerequisite {} hashes to {actual}",
353 prerequisite.oid
354 )));
355 }
356 }
357 Err(GitError::NotFound(_)) => missing.push(prerequisite.oid),
358 Err(err) => return Err(err),
359 }
360 }
361 if missing.is_empty() {
362 return Ok(());
363 }
364 Err(GitError::object_not_found_in(
365 missing[0],
366 MissingObjectContext::PackInstall,
367 ))
368}
369
370pub fn unbundle_objects<R, W>(
371 bundle: &Bundle,
372 prerequisite_reader: &R,
373 writer: &mut W,
374) -> Result<BundleUnbundleResult>
375where
376 R: ObjectReader,
377 W: ObjectWriter,
378{
379 verify_bundle_prerequisites(bundle, prerequisite_reader)?;
380 let pack = PackFile::parse_bundle(bundle)?;
381 let written_objects = write_pack_objects(pack, writer, "bundle")?.written_objects;
382 Ok(BundleUnbundleResult {
383 written_objects,
384 references: bundle.references.clone(),
385 })
386}
387
388pub fn install_bundle_pack<R>(
389 bundle: &Bundle,
390 prerequisite_reader: &R,
391 destination: &impl RawPackInstaller,
392) -> Result<BundleUnbundleResult>
393where
394 R: ObjectReader,
395{
396 verify_bundle_prerequisites(bundle, prerequisite_reader)?;
397 let mut reader = bundle.pack.as_slice();
398 let install = destination.install_raw_pack_from_reader(&mut reader)?;
399 Ok(BundleUnbundleResult {
400 written_objects: install.object_ids,
401 references: bundle.references.clone(),
402 })
403}
404
405pub fn unpack_packfile_objects<W>(
406 pack_bytes: &[u8],
407 format: ObjectFormat,
408 writer: &W,
409) -> Result<PackUnpackResult>
410where
411 W: ObjectWriter,
412{
413 let pack = PackFile::parse(pack_bytes, format)?;
414 write_pack_objects(pack, writer, "pack")
415}
416
417pub fn index_raw_pack(pack_bytes: &[u8], format: ObjectFormat) -> Result<RawPackIndexResult> {
418 let pack = PackFile::parse(pack_bytes, format)?;
419 let built = PackIndex::write_v2_for_pack(pack_bytes, format)?;
420 if built.pack_checksum != pack.checksum {
421 return Err(GitError::InvalidFormat(
422 "pack index checksum does not match parsed pack checksum".to_string(),
423 ));
424 }
425
426 let offsets = built
427 .entries
428 .iter()
429 .map(|entry| (entry.oid, entry.offset))
430 .collect::<HashMap<_, _>>();
431 let mut objects = Vec::with_capacity(pack.entries.len());
432 for object in pack.entries {
433 let offset = offsets.get(&object.entry.oid).copied().ok_or_else(|| {
434 GitError::InvalidFormat(format!(
435 "pack index is missing object {}",
436 object.entry.oid.to_hex()
437 ))
438 })?;
439 objects.push(RawPackIndexedObject {
440 oid: object.entry.oid,
441 object_type: object.object.object_type,
442 size: object.object.body.len() as u64,
443 offset,
444 });
445 }
446
447 Ok(RawPackIndexResult {
448 pack_id: built.pack_checksum,
449 index: built.index,
450 objects,
451 })
452}
453
454pub fn index_raw_pack_from_reader<R>(
455 reader: &mut R,
456 format: ObjectFormat,
457) -> Result<RawPackIndexResult>
458where
459 R: Read,
460{
461 Ok(stream_index_build_to_raw_result(
462 PackIndex::write_v2_for_pack_reader_to_trailer(reader, format)?,
463 ))
464}
465
466pub fn index_raw_pack_from_reader_with_len<R>(
467 reader: &mut R,
468 format: ObjectFormat,
469 pack_len: u64,
470) -> Result<RawPackIndexResult>
471where
472 R: Read,
473{
474 Ok(stream_index_build_to_raw_result(
475 PackIndex::write_v2_for_pack_reader_with_len(reader, format, pack_len)?,
476 ))
477}
478
479pub fn index_raw_pack_file(
480 path: impl AsRef<Path>,
481 format: ObjectFormat,
482) -> Result<RawPackIndexResult> {
483 Ok(stream_index_build_to_raw_result(
484 PackIndex::write_v2_for_pack_path(path, format)?,
485 ))
486}
487
488fn stream_index_build_to_raw_result(built: PackStreamIndexBuild) -> RawPackIndexResult {
489 let objects = built
490 .objects
491 .into_iter()
492 .map(|object| RawPackIndexedObject {
493 oid: object.oid,
494 object_type: object.object_type,
495 size: object.size,
496 offset: object.offset,
497 })
498 .collect::<Vec<_>>();
499 RawPackIndexResult {
500 pack_id: built.pack_checksum,
501 index: built.index,
502 objects,
503 }
504}
505
506fn write_pack_objects<W>(pack: PackFile, writer: &W, source: &str) -> Result<PackUnpackResult>
507where
508 W: ObjectWriter,
509{
510 let mut written_objects = Vec::with_capacity(pack.entries.len());
511 for entry in pack.entries {
512 let expected = entry.entry.oid;
513 let actual = writer.write_object(entry.object)?;
514 if actual != expected {
515 return Err(GitError::InvalidObject(format!(
516 "{source} object id mismatch: expected {expected}, wrote {actual}"
517 )));
518 }
519 written_objects.push(actual);
520 }
521 Ok(PackUnpackResult { written_objects })
522}
523pub(crate) fn validate_pack_checksum(
524 pack: &[u8],
525 format: ObjectFormat,
526 expected: &ObjectId,
527 context: &str,
528) -> Result<()> {
529 if expected.format() != format {
530 return Err(GitError::InvalidObjectId(format!(
531 "{context} checksum format does not match object format"
532 )));
533 }
534 let hash_len = format.raw_len();
535 if pack.len() < 12 + hash_len {
536 return Err(GitError::InvalidFormat(format!(
537 "{context} pack file too short"
538 )));
539 }
540 if &pack[..4] != b"PACK" {
541 return Err(GitError::InvalidFormat(format!(
542 "{context} pack file missing PACK signature"
543 )));
544 }
545 let trailer_offset = pack.len() - hash_len;
546 let actual = sley_core::digest_bytes(format, &pack[..trailer_offset])?;
547 let trailer = ObjectId::from_raw(format, &pack[trailer_offset..])?;
548 if &actual != expected || trailer != *expected {
549 return Err(GitError::InvalidFormat(format!(
550 "{context} pack checksum does not match generated pack"
551 )));
552 }
553 Ok(())
554}
555
556impl FileObjectDatabase {
557 pub fn install_pack(&self, pack: &PackWrite) -> Result<PackInstallResult> {
558 self.install_pack_with_options(pack, RawPackInstallOptions::default())
559 }
560
561 pub fn write_blob_as_pack(
562 &self,
563 oid: ObjectId,
564 object: &EncodedObject,
565 compression_level: u32,
566 ) -> Result<ObjectId> {
567 if object.object_type != ObjectType::Blob {
568 return Err(GitError::InvalidObject(
569 "write_blob_as_pack requires a blob object".into(),
570 ));
571 }
572 if oid.format() != self.format {
573 return Err(GitError::InvalidObjectId(format!(
574 "object {oid} uses {}, store uses {}",
575 oid.format().name(),
576 self.format.name()
577 )));
578 }
579 if self.contains(&oid)? {
580 return Ok(oid);
581 }
582 let input = [PackInput { oid: &oid, object }];
583 let options = PackWriteOptions::new()
584 .with_window(0)
585 .with_depth(0)
586 .with_reorder(false)
587 .with_compression_level(compression_level);
588 let pack =
589 PackFile::write_packed_with_known_ids_and_options(&input, self.format, &options)?;
590 self.install_pack(&pack)?;
591 Ok(oid)
592 }
593
594 pub fn write_blobs_as_pack(
595 &self,
596 objects: &[(ObjectId, EncodedObject)],
597 compression_level: u32,
598 ) -> Result<()> {
599 let mut seen = HashSet::with_capacity(objects.len());
600 let mut inputs = Vec::new();
601 for (oid, object) in objects {
602 if object.object_type != ObjectType::Blob {
603 return Err(GitError::InvalidObject(
604 "write_blobs_as_pack requires blob objects".into(),
605 ));
606 }
607 if oid.format() != self.format {
608 return Err(GitError::InvalidObjectId(format!(
609 "object {oid} uses {}, store uses {}",
610 oid.format().name(),
611 self.format.name()
612 )));
613 }
614 if seen.insert(*oid) && !self.contains(oid)? {
615 inputs.push(PackInput { oid, object });
616 }
617 }
618 if inputs.is_empty() {
619 return Ok(());
620 }
621 let options = PackWriteOptions::new()
622 .with_window(0)
623 .with_depth(0)
624 .with_reorder(false)
625 .with_compression_level(compression_level);
626 let pack =
627 PackFile::write_packed_with_known_ids_and_options(&inputs, self.format, &options)?;
628 self.install_pack(&pack)?;
629 Ok(())
630 }
631
632 pub fn install_pack_with_options(
633 &self,
634 pack: &PackWrite,
635 options: RawPackInstallOptions,
636 ) -> Result<PackInstallResult> {
637 if pack.checksum.format() != self.format {
638 return Err(GitError::InvalidObjectId(format!(
639 "pack checksum uses {}, store uses {}",
640 pack.checksum.format().name(),
641 self.format.name()
642 )));
643 }
644 for entry in &pack.entries {
645 if entry.oid.format() != self.format {
646 return Err(GitError::InvalidObjectId(format!(
647 "pack entry {} uses {}, store uses {}",
648 entry.oid,
649 entry.oid.format().name(),
650 self.format.name()
651 )));
652 }
653 }
654 let canonical_index = PackIndex::write_v2_for_pack(&pack.pack, self.format)?;
655 let parsed_index = PackIndex::parse(&pack.index, self.format)?;
656 if canonical_index.pack_checksum != pack.checksum
657 || parsed_index.pack_checksum != pack.checksum
658 {
659 return Err(GitError::InvalidFormat(
660 "pack and index checksums do not match pack write".into(),
661 ));
662 }
663 if pack.index != canonical_index.index {
664 return Err(GitError::InvalidFormat(
665 "pack index does not match pack contents".into(),
666 ));
667 }
668
669 let pack_dir = self.objects_dir.join("pack");
670 fs::create_dir_all(&pack_dir)?;
671 let pack_name = format!("pack-{}", pack.checksum.to_hex());
672 let pack_path = pack_dir.join(format!("{pack_name}.pack"));
673 let index_path = pack_dir.join(format!("{pack_name}.idx"));
674 if !pack_path.exists() || !index_path.exists() {
675 write_pack_component(&pack_path, &pack.pack)?;
676 write_pack_component(&index_path, &pack.index)?;
677 }
678 let promisor_path = write_promisor_pack_sidecar(&pack_dir, &pack_name, options.promisor)?;
679 Ok(PackInstallResult {
680 pack_name,
681 pack_path,
682 index_path,
683 promisor_path,
684 object_ids: canonical_index
685 .entries
686 .iter()
687 .map(|entry| entry.oid)
688 .collect(),
689 })
690 }
691
692 pub fn install_written_pack(&self, pack: &PackWrite) -> Result<PackInstallResult> {
700 self.install_written_pack_with_options(pack, RawPackInstallOptions::default())
701 }
702
703 pub fn install_written_pack_with_options(
704 &self,
705 pack: &PackWrite,
706 options: RawPackInstallOptions,
707 ) -> Result<PackInstallResult> {
708 validate_pack_checksum(&pack.pack, self.format, &pack.checksum, "pack write")?;
709 let parsed_index = PackIndex::parse(&pack.index, self.format)?;
710 if parsed_index.pack_checksum != pack.checksum {
711 return Err(GitError::InvalidFormat(
712 "pack write index checksum does not match pack".into(),
713 ));
714 }
715 if !pack_index_entries_match_writer(&parsed_index.entries, &pack.entries) {
716 return Err(GitError::InvalidFormat(
717 "pack write index does not match generated entries".into(),
718 ));
719 }
720 self.install_generated_pack_unchecked(pack, options)
721 }
722
723 fn install_generated_pack_unchecked(
724 &self,
725 pack: &PackWrite,
726 options: RawPackInstallOptions,
727 ) -> Result<PackInstallResult> {
728 let pack_dir = self.objects_dir.join("pack");
729 fs::create_dir_all(&pack_dir)?;
730 let pack_name = format!("pack-{}", pack.checksum.to_hex());
731 let pack_path = pack_dir.join(format!("{pack_name}.pack"));
732 let index_path = pack_dir.join(format!("{pack_name}.idx"));
733 if !pack_path.exists() || !index_path.exists() {
734 write_pack_component(&pack_path, &pack.pack)?;
735 write_pack_component(&index_path, &pack.index)?;
736 }
737 let promisor_path = write_promisor_pack_sidecar(&pack_dir, &pack_name, options.promisor)?;
738 Ok(PackInstallResult {
739 pack_name,
740 pack_path,
741 index_path,
742 promisor_path,
743 object_ids: pack.entries.iter().map(|entry| entry.oid).collect(),
744 })
745 }
746
747 pub(crate) fn install_pack_file_from_temp(
748 &self,
749 temp_pack_path: &Path,
750 pack_checksum: ObjectId,
751 index: &[u8],
752 object_ids: Vec<ObjectId>,
753 options: RawPackInstallOptions,
754 ) -> Result<PackInstallResult> {
755 let pack_dir = self.objects_dir.join("pack");
756 fs::create_dir_all(&pack_dir)?;
757 let pack_name = format!("pack-{}", pack_checksum.to_hex());
758 let pack_path = pack_dir.join(format!("{pack_name}.pack"));
759 let index_path = pack_dir.join(format!("{pack_name}.idx"));
760 match fs::rename(temp_pack_path, &pack_path) {
761 Ok(()) => {}
762 Err(_) if pack_path.exists() => {
763 let _ = fs::remove_file(temp_pack_path);
764 }
765 Err(err) => return Err(GitError::Io(err.to_string())),
766 }
767 write_pack_component(&index_path, index)?;
768 let promisor_path = write_promisor_pack_sidecar(&pack_dir, &pack_name, options.promisor)?;
769 Ok(PackInstallResult {
770 pack_name,
771 pack_path,
772 index_path,
773 promisor_path,
774 object_ids,
775 })
776 }
777
778 pub fn install_raw_pack_from_reader<R>(&self, reader: &mut R) -> Result<PackInstallResult>
779 where
780 R: Read,
781 {
782 self.install_raw_pack_from_reader_with_options(reader, RawPackInstallOptions::default())
783 }
784
785 pub fn begin_raw_pack_install(
786 &self,
787 expected_pack_id: ObjectId,
788 expected_pack_size: u64,
789 ) -> Result<RawPackStreamingInstall> {
790 self.begin_raw_pack_install_with_options(
791 expected_pack_id,
792 expected_pack_size,
793 RawPackInstallOptions::default(),
794 )
795 }
796
797 pub fn begin_raw_pack_install_with_options(
798 &self,
799 expected_pack_id: ObjectId,
800 expected_pack_size: u64,
801 options: RawPackInstallOptions,
802 ) -> Result<RawPackStreamingInstall> {
803 if expected_pack_id.format() != self.format {
804 return Err(GitError::InvalidObjectId(format!(
805 "pack checksum uses {}, store uses {}",
806 expected_pack_id.format().name(),
807 self.format.name()
808 )));
809 }
810 let pack_dir = self.objects_dir.join("pack");
811 fs::create_dir_all(&pack_dir)?;
812 let pack_name = format!("pack-{}", expected_pack_id.to_hex());
813 let pack_path = pack_dir.join(format!("{pack_name}.pack"));
814 let index_path = pack_dir.join(format!("{pack_name}.idx"));
815 let temp_pack_path = unique_temp_path(&pack_dir);
816 let file = fs::OpenOptions::new()
817 .write(true)
818 .create_new(true)
819 .open(&temp_pack_path)?;
820 Ok(RawPackStreamingInstall {
821 format: self.format,
822 expected_pack_id,
823 expected_pack_size,
824 options,
825 pack_dir,
826 pack_name,
827 pack_path,
828 index_path,
829 temp_pack_path,
830 file: Some(file),
831 written: 0,
832 finished: false,
833 })
834 }
835
836 pub fn install_raw_pack_from_reader_with_options<R>(
837 &self,
838 reader: &mut R,
839 options: RawPackInstallOptions,
840 ) -> Result<PackInstallResult>
841 where
842 R: Read,
843 {
844 let pack_dir = self.objects_dir.join("pack");
845 fs::create_dir_all(&pack_dir)?;
846 let temp_pack_path = unique_temp_path(&pack_dir);
847 let result = (|| -> Result<PackInstallResult> {
848 let mut file = fs::OpenOptions::new()
851 .write(true)
852 .create_new(true)
853 .open(&temp_pack_path)?;
854 let built = {
855 let mut tee = PackInstallTeeReader {
856 reader,
857 writer: &mut file,
858 max_input_size: options.max_input_size,
859 written: 0,
860 };
861 PackIndex::write_v2_for_pack_reader_to_trailer(&mut tee, self.format)?
862 };
863 file.flush()?;
864 file.sync_all()?;
865 drop(file);
866
867 self.install_pack_file_from_temp(
868 &temp_pack_path,
869 built.pack_checksum,
870 &built.index,
871 built.entries.iter().map(|entry| entry.oid).collect(),
872 options,
873 )
874 })();
875 if result.is_err() {
876 let _ = fs::remove_file(&temp_pack_path);
877 }
878 result
879 }
880
881}
882
883pub(crate) fn write_pack_component(path: &Path, bytes: &[u8]) -> Result<()> {
884 if path.exists() {
885 return Ok(());
886 }
887 let parent = path
888 .parent()
889 .ok_or_else(|| GitError::InvalidPath("pack component path has no parent".into()))?;
890 fs::create_dir_all(parent)?;
891 let temp_path = unique_temp_path(parent);
892 let write_result = (|| -> Result<()> {
893 {
894 let mut file = fs::OpenOptions::new()
895 .write(true)
896 .create_new(true)
897 .open(&temp_path)?;
898 file.write_all(bytes)?;
899 file.sync_all()?;
900 }
901 match fs::rename(&temp_path, path) {
902 Ok(()) => Ok(()),
903 Err(_) if path.exists() => {
904 let _ = fs::remove_file(&temp_path);
905 Ok(())
906 }
907 Err(err) => Err(GitError::Io(err.to_string())),
908 }
909 })();
910 if write_result.is_err() {
911 let _ = fs::remove_file(&temp_path);
912 }
913 write_result
914}
915
916pub(crate) fn write_promisor_pack_sidecar(
917 pack_dir: &Path,
918 pack_name: &str,
919 promisor: bool,
920) -> Result<Option<PathBuf>> {
921 if !promisor {
922 return Ok(None);
923 }
924 let path = pack_dir.join(format!("{pack_name}.promisor"));
925 write_pack_component(&path, b"")?;
926 Ok(Some(path))
927}