Skip to main content

thin_fetch/
pack_decoder.rs

1use std::{collections::HashMap, io::BufRead};
2
3use gix_object::Kind;
4use gix_pack::data::{entry::Header, input::BytesToEntriesIter};
5
6use crate::error::{Error, WithError as _, bail, error};
7use crate::object::{Blob, Commit, Object, Tag, Tree};
8use crate::utils::as_hex;
9use crate::{HashKind, delta};
10
11#[derive(Clone, Debug)]
12struct RawObject {
13    kind: Kind,
14    data: Vec<u8>,
15}
16
17#[derive(Clone)]
18struct ObjectStore {
19    objects: HashMap<String, RawObject>,
20}
21
22impl ObjectStore {
23    pub fn new() -> Self {
24        Self {
25            objects: HashMap::new(),
26        }
27    }
28
29    pub fn get(&self, hash: &str) -> Option<&RawObject> {
30        self.objects.get(hash)
31    }
32
33    pub fn insert(&mut self, hash: String, kind: Kind, data: Vec<u8>) {
34        let obj = RawObject { kind, data };
35
36        self.objects.entry(hash).or_insert(obj);
37    }
38}
39
40pub struct PackDecoder<R: BufRead> {
41    reader: BytesToEntriesIter<R>,
42    objects: ObjectStore,
43    hash_kind: HashKind,
44}
45
46fn new_object(kind: Kind, data: &[u8], hash_kind: HashKind) -> Result<Object, Error> {
47    let obj = match kind {
48        Kind::Commit => {
49            let commit = Commit::parse(data, hash_kind)?;
50            Object::Commit(Box::new(commit))
51        }
52        Kind::Tree => {
53            let tree = Tree::parse(data, hash_kind)?;
54            Object::Tree(tree)
55        }
56        Kind::Tag => {
57            let tag = Tag::parse(data, hash_kind)?;
58            Object::Tag(tag)
59        }
60        Kind::Blob => {
61            let blob = Blob::new(data.to_vec(), hash_kind);
62            Object::Blob(blob)
63        }
64    };
65
66    Ok(obj)
67}
68
69/// Pack file decoder.
70///
71/// Implements an iterator over [`Object`], resolving deltas as it reads data.
72impl<R: BufRead> PackDecoder<R> {
73    /// Initialize a decoder of pack files.
74    ///
75    /// # Errors
76    ///
77    /// Returns an error when the pack header is invalid.
78    pub fn new(reader: R, hash_kind: HashKind) -> Result<Self, Error> {
79        let file = BytesToEntriesIter::new_from_header(
80            reader,
81            gix_pack::data::input::Mode::AsIs,
82            gix_pack::data::input::EntryDataMode::KeepAndCrc32,
83            hash_kind.into(),
84        )
85        .map_err(|err| error!("could not read header: {err}"))?;
86
87        let store = ObjectStore::new();
88
89        Ok(Self {
90            reader: file,
91            objects: store,
92            hash_kind,
93        })
94    }
95
96    fn get_next(&mut self) -> Result<Option<Object>, Error> {
97        let Some(hdr) = self.reader.next() else {
98            return Ok(None);
99        };
100
101        let entry = hdr.map_err(|err| error!("could not read header: {err}"))?;
102        let data = entry.compressed.with_err_msg("compressed data not found")?;
103
104        let size = usize::try_from(entry.decompressed_size).with_err(|| {
105            error!(
106                "compressed data size exceeds max usize: {}",
107                entry.decompressed_size
108            )
109        })?;
110
111        let mut raw_data = vec![0; size];
112
113        let mut inf = zlib_rs::Inflate::new(true, 15);
114        inf.decompress(&data, &mut raw_data, zlib_rs::InflateFlush::Finish)
115            .map_err(|err| error!("could not decompress: {err:?}"))?;
116
117        // eprintln!("Received object: {:?}", entry.header);
118
119        let obj = match entry.header {
120            Header::Commit => new_object(Kind::Commit, &raw_data, self.hash_kind)?,
121            Header::Tree => new_object(Kind::Tree, &raw_data, self.hash_kind)?,
122            Header::Tag => new_object(Kind::Tag, &raw_data, self.hash_kind)?,
123            Header::Blob => new_object(Kind::Blob, &raw_data, self.hash_kind)?,
124            Header::RefDelta { base_id } => {
125                let hash = as_hex(base_id.as_bytes());
126
127                let apply_to = self
128                    .objects
129                    .get(&hash)
130                    .with_err(|| error!("object not found: {hash}"))?;
131
132                let mut delta_data = raw_data.as_slice();
133
134                let (base_size, read) = delta::decode_header_size(delta_data)
135                    .with_err_msg("could not get delta base size")?;
136                delta_data = &delta_data[read..];
137
138                let (target_size, read) = delta::decode_header_size(delta_data)
139                    .with_err_msg("could not get delta target size")?;
140                delta_data = &delta_data[read..];
141
142                if base_size != apply_to.data.len() {
143                    bail!(
144                        "delta base size mismatch: expected {base_size}, got {}",
145                        apply_to.data.len()
146                    );
147                }
148
149                let mut target = vec![0; target_size];
150
151                delta::apply(&apply_to.data, &mut target, delta_data)
152                    .with_err_msg("could not apply delta")?;
153
154                raw_data.clone_from(&target);
155
156                new_object(apply_to.kind, &target, self.hash_kind)?
157            }
158            Header::OfsDelta { base_distance: _ } => {
159                bail!("ofs-delta not supported")
160            }
161        };
162
163        let hash = as_hex(obj.hash().as_ref());
164        self.objects.insert(hash, obj.kind(), raw_data);
165
166        Ok(Some(obj))
167    }
168}
169
170impl<R: BufRead> Iterator for PackDecoder<R> {
171    type Item = Result<Object, Error>;
172
173    fn next(&mut self) -> Option<Self::Item> {
174        self.get_next().transpose()
175    }
176}