git_zero_core/pack/
index.rs1use super::{IndexEntry, LooseEncoder, crc::Crc32, encode};
3use crate::{Error, ObjectId, Repository, Result, io::Write, object::Hash};
4
5pub fn write_indexed_with_scratch(
19 repository: &impl Repository,
20 order: Option<&[usize]>,
21 entries: &mut [IndexEntry],
22 scratch: &mut [u8],
23 pack_output: &mut impl Write,
24 index_output: &mut impl Write,
25) -> Result<ObjectId> {
26 let entries = prepare_entries(repository, order, entries, scratch)?;
27 let mut output = PackWriter {
28 output: pack_output,
29 hash: repository.object_format().hash(),
30 offset: 0,
31 crc: Crc32::new(),
32 };
33 output.write_all(b"PACK")?;
34 output.write_all(&2_u32.to_be_bytes())?;
35 output.write_all(&(entries.len() as u32).to_be_bytes())?;
36 for (position, entry) in entries.iter_mut().enumerate() {
37 let index = order.map_or(position, |order| order[position]);
38 *entry = output.write_entry(repository, index, entry.id, scratch)?;
39 }
40 let checksum = output.hash.finish();
41 output.output.write_all(checksum.as_bytes())?;
42 entries.sort_unstable_by_key(|entry| entry.id);
43 write_index(entries, checksum, index_output)?;
44 Ok(checksum)
45}
46
47fn prepare_entries<'entries>(
48 repository: &impl Repository,
49 order: Option<&[usize]>,
50 entries: &'entries mut [IndexEntry],
51 scratch: &[u8],
52) -> Result<&'entries mut [IndexEntry]> {
53 if let Some(order) = order {
54 encode::validate_order(repository, order)?;
55 }
56 let count = order.map_or(repository.object_count(), <[usize]>::len);
57 u32::try_from(count).map_err(|_| Error::Unsupported)?;
58 let entries = entries.get_mut(..count).ok_or(Error::BufferTooSmall)?;
59 if count != 0 && scratch.is_empty() {
60 return Err(Error::BufferTooSmall);
61 }
62 for (position, entry) in entries.iter_mut().enumerate() {
63 let index = order.map_or(position, |order| order[position]);
64 let id = repository.object_id(index).ok_or(Error::ObjectNotFound)?;
65 if id.format() != repository.object_format() {
66 return Err(Error::InvalidObjectId);
67 }
68 repository.object_info(index)?;
69 *entry = IndexEntry {
70 id,
71 offset: position as u64,
74 crc32: 0,
75 };
76 }
77 entries.sort_unstable_by_key(|entry| entry.id);
78 if entries.windows(2).any(|pair| pair[0].id == pair[1].id) {
79 return Err(Error::InvalidObject);
80 }
81 entries.sort_unstable_by_key(|entry| entry.offset);
82
83 Ok(entries)
84}
85
86pub fn write_index(
93 entries: &[IndexEntry],
94 pack_checksum: ObjectId,
95 output: &mut impl Write,
96) -> Result<ObjectId> {
97 validate_entries(entries, pack_checksum)?;
98 let format = pack_checksum.format();
99 let mut output = IndexWriter {
100 output,
101 hash: format.hash(),
102 };
103 output.write_all(b"\xfftOc")?;
104 output.write_all(&2_u32.to_be_bytes())?;
105 output.write_fanout(entries)?;
106 for entry in entries {
107 output.write_all(entry.id.as_bytes())?;
108 }
109 for entry in entries {
110 output.write_all(&entry.crc32.to_be_bytes())?;
111 }
112 output.write_offsets(entries)?;
113 output.write_all(pack_checksum.as_bytes())?;
114 let checksum = output.hash.finish();
115 output.output.write_all(checksum.as_bytes())?;
116 Ok(checksum)
117}
118
119pub(super) fn validate_entries(entries: &[IndexEntry], pack_checksum: ObjectId) -> Result<()> {
120 u32::try_from(entries.len()).map_err(|_| Error::Unsupported)?;
121 let format = pack_checksum.format();
122 if entries
123 .iter()
124 .any(|entry| entry.id.format() != format || entry.offset < 12)
125 || entries.windows(2).any(|pair| pair[0].id >= pair[1].id)
126 {
127 return Err(Error::InvalidObject);
128 }
129 if entries
131 .iter()
132 .filter(|entry| entry.offset >= 0x8000_0000)
133 .count() as u64
134 > 0x8000_0000
135 {
136 return Err(Error::Unsupported);
137 }
138 Ok(())
139}
140
141struct PackWriter<'a, W> {
142 output: &'a mut W,
143 hash: Hash,
144 offset: u64,
145 crc: Crc32,
146}
147
148impl<W: Write> PackWriter<'_, W> {
149 fn write_entry(
150 &mut self,
151 repository: &impl Repository,
152 index: usize,
153 id: ObjectId,
154 scratch: &mut [u8],
155 ) -> Result<IndexEntry> {
156 let info = repository.object_info(index)?;
157 let offset = self.offset;
158 self.crc = Crc32::new();
159 encode::write_entry_header(info, self)?;
160 let mut encoder = LooseEncoder::from_info(index, info, false)?;
161 loop {
162 let count = encoder.read(repository, scratch)?;
163 if count == 0 {
164 break;
165 }
166 self.write_all(&scratch[..count])?;
167 }
168 Ok(IndexEntry {
169 id,
170 offset,
171 crc32: self.crc.value(),
172 })
173 }
174}
175
176impl<W: Write> Write for PackWriter<'_, W> {
177 fn write_all(&mut self, bytes: &[u8]) -> Result<()> {
178 let end = self
179 .offset
180 .checked_add(bytes.len() as u64)
181 .ok_or(Error::LimitExceeded)?;
182 self.output.write_all(bytes)?;
183 self.hash.update(bytes);
184 self.crc.update(bytes);
185 self.offset = end;
186 Ok(())
187 }
188}
189
190struct IndexWriter<'a, W> {
191 output: &'a mut W,
192 hash: Hash,
193}
194
195impl<W: Write> IndexWriter<'_, W> {
196 fn write_fanout(&mut self, entries: &[IndexEntry]) -> Result<()> {
197 let mut position = 0;
198 for first in 0..=255_u8 {
199 while position < entries.len() && entries[position].id.as_bytes()[0] <= first {
200 position += 1;
201 }
202 self.write_all(&(position as u32).to_be_bytes())?;
203 }
204 Ok(())
205 }
206
207 fn write_offsets(&mut self, entries: &[IndexEntry]) -> Result<()> {
208 let mut large = 0_u32;
209 for entry in entries {
210 let offset = if entry.offset < 0x8000_0000 {
211 entry.offset as u32
212 } else {
213 let offset = 0x8000_0000 | large;
214 large += 1;
215 offset
216 };
217 self.write_all(&offset.to_be_bytes())?;
218 }
219 for entry in entries.iter().filter(|entry| entry.offset >= 0x8000_0000) {
220 self.write_all(&entry.offset.to_be_bytes())?;
221 }
222 Ok(())
223 }
224}
225
226impl<W: Write> Write for IndexWriter<'_, W> {
227 fn write_all(&mut self, bytes: &[u8]) -> Result<()> {
228 self.output.write_all(bytes)?;
229 self.hash.update(bytes);
230 Ok(())
231 }
232}