1use super::*;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct FixThinPackBuild {
8 pub pack: Vec<u8>,
11 pub index: PackIndexBuild,
13 pub appended_bases: Vec<ObjectId>,
15}
16
17pub fn fix_thin_pack<F>(
29 pack_bytes: &[u8],
30 format: ObjectFormat,
31 external_base: F,
32) -> Result<FixThinPackBuild>
33where
34 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
35{
36 fix_thin_pack_with_limits(pack_bytes, format, external_base, PackReadLimits::default())
37}
38
39pub fn fix_thin_pack_with_limits<F>(
41 pack_bytes: &[u8],
42 format: ObjectFormat,
43 mut external_base: F,
44 limits: PackReadLimits,
45) -> Result<FixThinPackBuild>
46where
47 F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
48{
49 if let Ok(index) = PackIndex::write_v2_for_pack_with_limits(pack_bytes, format, limits) {
50 return Ok(FixThinPackBuild {
51 pack: pack_bytes.to_vec(),
52 index,
53 appended_bases: Vec::new(),
54 });
55 }
56
57 let mut resolved_external = HashMap::<ObjectId, Option<EncodedObject>>::new();
58 let mut external_order = Vec::new();
59 let thin_index = PackIndex::write_v2_for_pack_with_base_and_limits(
60 pack_bytes,
61 format,
62 |oid| {
63 if let Some(object) = resolved_external.get(oid) {
64 return Ok(object.clone());
65 }
66 let object = external_base(oid)?;
67 if let Some(object) = &object {
68 let actual = object.object_id(format)?;
69 if actual != *oid {
70 return Err(GitError::InvalidObject(format!(
71 "external base {oid} resolved to object {actual}"
72 )));
73 }
74 external_order.push(*oid);
75 }
76 resolved_external.insert(*oid, object.clone());
77 Ok(object)
78 },
79 limits,
80 )?;
81
82 let body_oids = thin_index
83 .entries
84 .iter()
85 .map(|entry| entry.oid)
86 .collect::<HashSet<_>>();
87 let appended_bases = external_order
88 .into_iter()
89 .filter(|oid| !body_oids.contains(oid))
90 .collect::<Vec<_>>();
91
92 let trailer_len = format.raw_len();
93 let trailer_offset = pack_bytes
94 .len()
95 .checked_sub(trailer_len)
96 .ok_or_else(|| GitError::InvalidFormat("pack file too short".into()))?;
97 let old_count = u32_be(&pack_bytes[8..12]);
98 let append_count = u32::try_from(appended_bases.len())
99 .map_err(|_| GitError::InvalidFormat("too many external pack bases".into()))?;
100 let new_count = old_count
101 .checked_add(append_count)
102 .ok_or_else(|| GitError::InvalidFormat("pack object count overflow".into()))?;
103
104 let mut pack = Vec::with_capacity(pack_bytes.len());
105 pack.extend_from_slice(&pack_bytes[..trailer_offset]);
106 pack[8..12].copy_from_slice(&new_count.to_be_bytes());
107 for oid in &appended_bases {
108 let object = resolved_external
109 .get(oid)
110 .and_then(Option::as_ref)
111 .ok_or_else(|| GitError::not_found(format!("external pack base {oid}")))?;
112 write_entry_header(&mut pack, object.object_type, object.body.len() as u64);
113 write_compressed_payload(&mut pack, &object.body, 6)?;
114 }
115 let checksum = sley_core::digest_bytes(format, &pack)?;
116 pack.extend_from_slice(checksum.as_bytes());
117
118 let index = PackIndex::write_v2_for_pack_with_limits(&pack, format, limits)?;
121 Ok(FixThinPackBuild {
122 pack,
123 index,
124 appended_bases,
125 })
126}