gix_pack/data/output/entry/
mod.rs1use std::io::Write;
2
3use gix_hash::ObjectId;
4
5use crate::{data, data::output, find};
6
7pub mod iter_from_counts;
9pub use iter_from_counts::function::iter_from_counts;
10
11#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub enum Kind {
15 Base(gix_object::Kind),
17 DeltaRef {
20 object_index: usize,
23 },
24 DeltaOid {
29 id: ObjectId,
31 },
32}
33
34#[expect(missing_docs)]
36#[derive(Debug, thiserror::Error)]
37pub enum Error {
38 #[error("{0}")]
39 ZlibDeflate(#[from] std::io::Error),
40 #[error(transparent)]
41 EntryType(#[from] crate::data::entry::decode::Error),
42}
43
44impl output::Entry {
45 pub fn invalid() -> output::Entry {
47 output::Entry {
48 id: gix_hash::Kind::shortest().null(), kind: Kind::Base(gix_object::Kind::Blob),
50 decompressed_size: 0,
51 compressed_data: vec![],
52 }
53 }
54
55 pub fn is_invalid(&self) -> bool {
60 self.id.is_null()
61 }
62
63 pub fn from_pack_entry(
66 mut entry: find::Entry,
67 count: &output::Count,
68 potential_bases: &[output::Count],
69 bases_index_offset: usize,
70 pack_offset_to_oid: Option<impl FnMut(u32, u64) -> Option<ObjectId>>,
71 target_version: data::Version,
72 ) -> Option<Result<Self, Error>> {
73 if entry.version != target_version {
74 return None;
75 }
76
77 let pack_offset_must_be_zero = 0;
78 let pack_entry = match data::Entry::from_bytes(&entry.data, pack_offset_must_be_zero, count.id.kind()) {
79 Ok(e) => e,
80 Err(err) => return Some(Err(err.into())),
81 };
82
83 use crate::data::entry::Header::*;
84 match pack_entry.header {
85 Commit => Some(output::entry::Kind::Base(gix_object::Kind::Commit)),
86 Tree => Some(output::entry::Kind::Base(gix_object::Kind::Tree)),
87 Blob => Some(output::entry::Kind::Base(gix_object::Kind::Blob)),
88 Tag => Some(output::entry::Kind::Base(gix_object::Kind::Tag)),
89 OfsDelta { base_distance } => {
90 let pack_location = count.entry_pack_location.as_ref().expect("packed");
91 let base_offset = pack_location
92 .pack_offset
93 .checked_sub(base_distance)
94 .expect("pack-offset - distance is firmly within the pack");
95 potential_bases
96 .binary_search_by(|e| {
97 e.entry_pack_location
98 .as_ref()
99 .expect("packed")
100 .pack_offset
101 .cmp(&base_offset)
102 })
103 .ok()
104 .map(|idx| output::entry::Kind::DeltaRef {
105 object_index: idx + bases_index_offset,
106 })
107 .or_else(|| {
108 pack_offset_to_oid
109 .and_then(|mut f| f(pack_location.pack_id, base_offset))
110 .map(|id| output::entry::Kind::DeltaOid { id })
111 })
112 }
113 RefDelta { base_id: _ } => None, }
115 .map(|kind| {
116 Ok(output::Entry {
117 id: count.id.to_owned(),
118 kind,
119 decompressed_size: pack_entry.decompressed_size as usize,
120 compressed_data: {
121 entry.data.copy_within(pack_entry.data_offset as usize.., 0);
122 entry.data.resize(
123 entry.data.len()
124 - usize::try_from(pack_entry.data_offset).expect("offset representable as usize"),
125 0,
126 );
127 entry.data
128 },
129 })
130 })
131 }
132
133 pub fn from_data(
139 count: &output::Count,
140 obj: &gix_object::Data<'_>,
141 compression: gix_zlib::Compression,
142 ) -> Result<Self, Error> {
143 Ok(output::Entry {
144 id: count.id.to_owned(),
145 kind: Kind::Base(obj.kind),
146 decompressed_size: obj.data.len(),
147 compressed_data: {
148 let mut out = gix_zlib::stream::deflate::Write::new(Vec::new(), compression);
149 if let Err(err) = std::io::copy(&mut &*obj.data, &mut out) {
150 match err.kind() {
151 std::io::ErrorKind::Other => return Err(Error::ZlibDeflate(err)),
152 err => unreachable!("Should never see other errors than zlib, but got {:?}", err),
153 }
154 }
155 out.flush()?;
156 out.into_inner()
157 },
158 })
159 }
160
161 pub fn to_entry_header(
167 &self,
168 version: data::Version,
169 index_to_base_distance: impl FnOnce(usize) -> u64,
170 ) -> data::entry::Header {
171 assert!(
172 matches!(version, data::Version::V2),
173 "we can only write V2 pack entries for now"
174 );
175
176 use Kind::*;
177 match self.kind {
178 Base(kind) => {
179 use gix_object::Kind::*;
180 match kind {
181 Tree => data::entry::Header::Tree,
182 Blob => data::entry::Header::Blob,
183 Commit => data::entry::Header::Commit,
184 Tag => data::entry::Header::Tag,
185 }
186 }
187 DeltaOid { id } => data::entry::Header::RefDelta { base_id: id.to_owned() },
188 DeltaRef { object_index } => data::entry::Header::OfsDelta {
189 base_distance: index_to_base_distance(object_index),
190 },
191 }
192 }
193}