git_pack/data/output/
bytes.rs1use std::io::Write;
2
3use git_features::hash;
4
5use crate::data::output;
6
7#[allow(missing_docs)]
9#[derive(Debug, thiserror::Error)]
10pub enum Error<E>
11where
12 E: std::error::Error + 'static,
13{
14 #[error(transparent)]
15 Io(#[from] std::io::Error),
16 #[error(transparent)]
17 Input(E),
18}
19
20pub struct FromEntriesIter<I, W> {
23 pub input: I,
25 output: hash::Write<W>,
27 trailer: Option<git_hash::ObjectId>,
29 header_info: Option<(crate::data::Version, u32)>,
32 entry_version: crate::data::Version,
34 written: u64,
36 pack_offsets_and_validity: Vec<(u64, bool)>,
40 is_done: bool,
42}
43
44impl<I, W, E> FromEntriesIter<I, W>
45where
46 I: Iterator<Item = Result<Vec<output::Entry>, E>>,
47 W: std::io::Write,
48 E: std::error::Error + 'static,
49{
50 pub fn new(
61 input: I,
62 output: W,
63 num_entries: u32,
64 version: crate::data::Version,
65 object_hash: git_hash::Kind,
66 ) -> Self {
67 assert!(
68 matches!(version, crate::data::Version::V2),
69 "currently only pack version 2 can be written",
70 );
71 FromEntriesIter {
72 input,
73 output: hash::Write::new(output, object_hash),
74 trailer: None,
75 entry_version: version,
76 pack_offsets_and_validity: Vec::with_capacity(num_entries as usize),
77 written: 0,
78 header_info: Some((version, num_entries)),
79 is_done: false,
80 }
81 }
82
83 pub fn into_write(self) -> W {
87 self.output.inner
88 }
89
90 pub fn digest(&self) -> Option<git_hash::ObjectId> {
93 self.trailer
94 }
95
96 fn next_inner(&mut self) -> Result<u64, Error<E>> {
97 let previous_written = self.written;
98 if let Some((version, num_entries)) = self.header_info.take() {
99 let header_bytes = crate::data::header::encode(version, num_entries);
100 self.output.write_all(&header_bytes[..])?;
101 self.written += header_bytes.len() as u64;
102 }
103 match self.input.next() {
104 Some(entries) => {
105 for entry in entries.map_err(Error::Input)? {
106 if entry.is_invalid() {
107 self.pack_offsets_and_validity.push((0, false));
108 continue;
109 };
110 self.pack_offsets_and_validity.push((self.written, true));
111 let header = entry.to_entry_header(self.entry_version, |index| {
112 let (base_offset, is_valid_object) = self.pack_offsets_and_validity[index];
113 if !is_valid_object {
114 unreachable!("if you see this the object database is correct as a delta refers to a non-existing object")
115 }
116 self.written - base_offset
117 });
118 self.written += header.write_to(entry.decompressed_size as u64, &mut self.output)? as u64;
119 self.written += std::io::copy(&mut &*entry.compressed_data, &mut self.output)?;
120 }
121 }
122 None => {
123 let digest = self.output.hash.clone().digest();
124 self.output.inner.write_all(&digest[..])?;
125 self.written += digest.len() as u64;
126 self.output.inner.flush()?;
127 self.is_done = true;
128 self.trailer = Some(git_hash::ObjectId::from(digest));
129 }
130 };
131 Ok(self.written - previous_written)
132 }
133}
134
135impl<I, W, E> Iterator for FromEntriesIter<I, W>
136where
137 I: Iterator<Item = Result<Vec<output::Entry>, E>>,
138 W: std::io::Write,
139 E: std::error::Error + 'static,
140{
141 type Item = Result<u64, Error<E>>;
143
144 fn next(&mut self) -> Option<Self::Item> {
145 if self.is_done {
146 return None;
147 }
148 Some(match self.next_inner() {
149 Err(err) => {
150 self.is_done = true;
151 Err(err)
152 }
153 Ok(written) => Ok(written),
154 })
155 }
156}