use super::*;
impl PackFile {
pub fn parse_sha1(bytes: &[u8]) -> Result<Self> {
Self::parse(bytes, ObjectFormat::Sha1)
}
pub fn parse(bytes: &[u8], format: ObjectFormat) -> Result<Self> {
Self::parse_with_base(bytes, format, |_| Ok(None))
}
pub fn parse_bundle(bundle: &Bundle) -> Result<Self> {
Self::parse(&bundle.pack, bundle.format)
}
pub fn index_pack(bytes: &[u8], format: ObjectFormat) -> Result<PackWrite> {
let PackIndexBuild {
index,
pack_checksum,
entries,
} = PackIndex::write_v2_for_pack(bytes, format)?;
Ok(PackWrite {
pack: bytes.to_vec(),
index,
checksum: pack_checksum,
entries,
delta_count: 0,
})
}
pub fn parse_thin<F>(bytes: &[u8], format: ObjectFormat, external_base: F) -> Result<Self>
where
F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
{
Self::parse_with_base(bytes, format, external_base)
}
pub(crate) fn parse_with_base<F>(bytes: &[u8], format: ObjectFormat, mut external_base: F) -> Result<Self>
where
F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
{
let trailer_len = format.raw_len();
if bytes.len() < 12 + trailer_len {
return Err(GitError::InvalidFormat("pack file too short".into()));
}
let trailer_offset = bytes.len() - trailer_len;
let checksum = sley_core::digest_bytes(format, &bytes[..trailer_offset])?;
let expected = ObjectId::from_raw(format, &bytes[trailer_offset..])?;
if checksum != expected {
return Err(GitError::InvalidFormat(format!(
"pack checksum mismatch: expected {expected}, got {checksum}"
)));
}
if &bytes[..4] != b"PACK" {
return Err(GitError::InvalidFormat("missing PACK signature".into()));
}
let version = u32_be(&bytes[4..8]);
if version != 2 && version != 3 {
return Err(GitError::Unsupported(format!("pack version {version}")));
}
let count = u32_be(&bytes[8..12]) as usize;
let mut offset = 12usize;
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
let entry_offset = offset;
let header = parse_entry_header(bytes, &mut offset)?;
let base =
match header.kind {
PackObjectKind::OfsDelta => Some(DeltaBase::Offset(
parse_ofs_delta_base_offset(bytes, &mut offset, entry_offset as u64)?,
)),
PackObjectKind::RefDelta => {
let hash_len = format.raw_len();
if offset + hash_len > trailer_offset {
return Err(GitError::InvalidFormat(
"truncated ref-delta base object id".into(),
));
}
let oid = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
offset += hash_len;
Some(DeltaBase::Ref(oid))
}
_ => None,
};
let mut body = Vec::new();
let consumed = inflate_into(
&bytes[offset..trailer_offset],
&mut body,
header.size.min(usize::MAX as u64) as usize,
)?;
if body.len() as u64 != header.size {
return Err(GitError::InvalidObject(format!(
"pack object declared {} bytes, decoded {}",
header.size,
body.len()
)));
}
if consumed == 0 {
return Err(GitError::InvalidFormat(
"empty compressed pack entry".into(),
));
}
offset = offset
.checked_add(consumed)
.ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
if offset > trailer_offset {
return Err(GitError::InvalidFormat(
"pack entry extends past checksum".into(),
));
}
if let Some(base) = base {
entries.push(ParsedPackEntry::Delta {
base,
compressed_size: consumed as u64,
delta_size: header.size,
offset: entry_offset as u64,
delta: body,
});
} else {
let object_type = match header.kind {
PackObjectKind::Commit => ObjectType::Commit,
PackObjectKind::Tree => ObjectType::Tree,
PackObjectKind::Blob => ObjectType::Blob,
PackObjectKind::Tag => ObjectType::Tag,
PackObjectKind::OfsDelta | PackObjectKind::RefDelta => unreachable!(),
};
let object = EncodedObject::new(object_type, body);
let oid = object.object_id(format)?;
entries.push(ParsedPackEntry::Resolved(PackObject {
entry: PackEntry {
oid,
compressed_size: consumed as u64,
uncompressed_size: header.size,
offset: entry_offset as u64,
},
object,
}));
}
}
if offset != trailer_offset {
return Err(GitError::InvalidFormat(format!(
"pack has {} trailing bytes before checksum",
trailer_offset - offset
)));
}
Ok(Self {
version,
entries: resolve_pack_entries(entries, format, &mut external_base)?,
checksum,
})
}
pub fn verify_pack_stats(bytes: &[u8], format: ObjectFormat) -> Result<PackVerifyStats> {
let pack = Self::parse(bytes, format)?;
let trailer_len = format.raw_len();
let trailer_offset = bytes.len() - trailer_len;
let count = u32_be(&bytes[8..12]) as usize;
let mut offset = 12usize;
let mut on_disk: Vec<OnDiskEntry> = Vec::with_capacity(count);
for _ in 0..count {
let entry_offset = offset as u64;
let header = parse_entry_header(bytes, &mut offset)?;
let stream_size = header.size;
let base =
match header.kind {
PackObjectKind::OfsDelta => Some(DeltaBase::Offset(
parse_ofs_delta_base_offset(bytes, &mut offset, entry_offset)?,
)),
PackObjectKind::RefDelta => {
let hash_len = format.raw_len();
if offset + hash_len > trailer_offset {
return Err(GitError::InvalidFormat(
"truncated ref-delta base object id".into(),
));
}
let oid = ObjectId::from_raw(format, &bytes[offset..offset + hash_len])?;
offset += hash_len;
Some(DeltaBase::Ref(oid))
}
_ => None,
};
let mut body = Vec::new();
let consumed = inflate_into(
&bytes[offset..trailer_offset],
&mut body,
header.size.min(usize::MAX as u64) as usize,
)?;
offset = offset
.checked_add(consumed)
.ok_or_else(|| GitError::InvalidFormat("pack offset overflow".into()))?;
on_disk.push(OnDiskEntry {
offset: entry_offset,
base,
stream_size,
});
}
let mut resolved_by_offset: HashMap<u64, &PackObject> =
HashMap::with_capacity(pack.entries.len());
for object in &pack.entries {
resolved_by_offset.insert(object.entry.offset, object);
}
let mut oid_by_offset: HashMap<u64, ObjectId> = HashMap::with_capacity(on_disk.len());
for entry in &on_disk {
if let Some(object) = resolved_by_offset.get(&entry.offset) {
oid_by_offset.insert(entry.offset, object.entry.oid);
}
}
let mut index_by_offset: HashMap<u64, usize> = HashMap::with_capacity(on_disk.len());
for (idx, entry) in on_disk.iter().enumerate() {
index_by_offset.insert(entry.offset, idx);
}
let mut sorted_offsets: Vec<u64> = on_disk.iter().map(|entry| entry.offset).collect();
sorted_offsets.sort_unstable();
let mut next_offset: HashMap<u64, u64> = HashMap::with_capacity(sorted_offsets.len());
for window in sorted_offsets.windows(2) {
next_offset.insert(window[0], window[1]);
}
if let Some(last) = sorted_offsets.last() {
next_offset.insert(*last, trailer_offset as u64);
}
let mut depth = vec![None; on_disk.len()];
fn resolve_depth(
idx: usize,
on_disk: &[OnDiskEntry],
index_by_offset: &HashMap<u64, usize>,
offset_of_oid: &HashMap<ObjectId, u64>,
depth: &mut [Option<u32>],
) -> u32 {
if let Some(d) = depth[idx] {
return d;
}
let computed = match &on_disk[idx].base {
None => 0,
Some(base) => {
let base_idx = match base {
DeltaBase::Offset(off) => index_by_offset.get(off).copied(),
DeltaBase::Ref(oid) => offset_of_oid
.get(oid)
.and_then(|off| index_by_offset.get(off).copied()),
};
match base_idx {
Some(bi) => {
resolve_depth(bi, on_disk, index_by_offset, offset_of_oid, depth) + 1
}
None => 1,
}
}
};
depth[idx] = Some(computed);
computed
}
let mut offset_of_oid: HashMap<ObjectId, u64> = HashMap::with_capacity(oid_by_offset.len());
for (off, oid) in &oid_by_offset {
offset_of_oid.insert(*oid, *off);
}
for idx in 0..on_disk.len() {
resolve_depth(idx, &on_disk, &index_by_offset, &offset_of_oid, &mut depth);
}
let mut stats = Vec::with_capacity(on_disk.len());
for (idx, entry) in on_disk.iter().enumerate() {
let off = entry.offset;
let object = resolved_by_offset.get(&off).ok_or_else(|| {
GitError::InvalidFormat("pack offset missing from resolved set".into())
})?;
let size_in_pack = next_offset
.get(&off)
.copied()
.unwrap_or(trailer_offset as u64)
.saturating_sub(off);
let base_oid = match &entry.base {
None => None,
Some(DeltaBase::Offset(base_off)) => oid_by_offset.get(base_off).copied(),
Some(DeltaBase::Ref(oid)) => Some(*oid),
};
stats.push(PackVerifyStat {
oid: object.entry.oid,
object_type: object.object.object_type,
size: entry.stream_size,
size_in_pack,
offset: off,
delta_depth: depth[idx].unwrap_or(0),
base_oid,
});
}
stats.sort_by_key(|stat| stat.offset);
Ok(PackVerifyStats {
objects: stats,
checksum: pack.checksum,
})
}
}
pub trait PackDeltaCache {
fn get(&self, offset: u64) -> Option<Arc<EncodedObject>>;
fn insert(&self, offset: u64, object: Arc<EncodedObject>);
}
pub(crate) struct NoopDeltaCache;
impl PackDeltaCache for NoopDeltaCache {
fn get(&self, _offset: u64) -> Option<Arc<EncodedObject>> {
None
}
fn insert(&self, _offset: u64, _object: Arc<EncodedObject>) {}
}
thread_local! {
static INFLATE: RefCell<flate2::Decompress> = RefCell::new(flate2::Decompress::new(true));
}
pub(crate) fn inflate_into(compressed: &[u8], out: &mut Vec<u8>, size_hint: usize) -> Result<usize> {
INFLATE.with(|cell| {
let mut decompress = cell.borrow_mut();
decompress.reset(true);
out.reserve(inflate::bounded_inflate_reserve(size_hint, compressed.len()));
let mut input = compressed;
let mut consumed_total = 0usize;
loop {
if out.len() == out.capacity() {
out.reserve(out.len().max(64));
}
let before_in = decompress.total_in();
let before_out = decompress.total_out();
let status = decompress
.decompress_vec(input, out, flate2::FlushDecompress::None)
.map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
let consumed = (decompress.total_in() - before_in) as usize;
let produced = decompress.total_out() - before_out;
input = &input[consumed..];
consumed_total += consumed;
match status {
flate2::Status::StreamEnd => return Ok(consumed_total),
_ if consumed == 0 && produced == 0 => {
return Err(GitError::InvalidObject("truncated zlib stream".into()));
}
_ => {}
}
}
})
}
pub(crate) fn inflate_prefix(compressed: &[u8], max_out: usize, out: &mut Vec<u8>) -> Result<()> {
INFLATE.with(|cell| {
let mut decompress = cell.borrow_mut();
decompress.reset(true);
out.reserve(max_out.max(16));
let mut input = compressed;
while out.len() < max_out {
if out.len() == out.capacity() {
out.reserve(out.len().max(16));
}
let before_in = decompress.total_in();
let before_out = decompress.total_out();
let status = decompress
.decompress_vec(input, out, flate2::FlushDecompress::None)
.map_err(|err| GitError::InvalidObject(format!("zlib inflate failed: {err}")))?;
let consumed = (decompress.total_in() - before_in) as usize;
let produced = decompress.total_out() - before_out;
input = &input[consumed..];
if status == flate2::Status::StreamEnd || (consumed == 0 && produced == 0) {
break;
}
}
Ok(())
})
}
pub fn read_object_at_arc<F>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
resolve_ref_base: F,
) -> Result<Arc<EncodedObject>>
where
F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
{
read_object_at_with_cache_arc(
pack_bytes,
offset,
format,
resolve_ref_base,
&NoopDeltaCache,
)
}
pub fn read_object_at_with_cache_arc<F, C>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
mut resolve_ref_base: F,
cache: &C,
) -> Result<Arc<EncodedObject>>
where
F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
C: PackDeltaCache + ?Sized,
{
read_object_at_with_cache_and_ofs_base_arc(
pack_bytes,
offset,
format,
&mut resolve_ref_base,
|_offset| Ok(None),
cache,
)
}
pub fn read_object_at_with_cache_and_ofs_base_arc<F, G, C>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
mut resolve_ref_base: F,
mut resolve_ofs_base: G,
cache: &C,
) -> Result<Arc<EncodedObject>>
where
F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
C: PackDeltaCache + ?Sized,
{
read_object_at_inner(
pack_bytes,
offset,
format,
&mut resolve_ref_base,
&mut resolve_ofs_base,
cache,
)
}
pub fn read_object_at_with_ofs_base_arc<F, G>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
resolve_ref_base: F,
resolve_ofs_base: G,
) -> Result<Arc<EncodedObject>>
where
F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
{
read_object_at_with_cache_and_ofs_base_arc(
pack_bytes,
offset,
format,
resolve_ref_base,
resolve_ofs_base,
&NoopDeltaCache,
)
}
pub(crate) fn read_object_at_inner<F, G, C>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
resolve_ref_base: &mut F,
resolve_ofs_base: &mut G,
cache: &C,
) -> Result<Arc<EncodedObject>>
where
F: FnMut(&ObjectId) -> Result<Option<Arc<EncodedObject>>>,
G: FnMut(u64) -> Result<Option<Arc<EncodedObject>>>,
C: PackDeltaCache + ?Sized,
{
if let Some(object) = cache.get(offset) {
return Ok(object);
}
let trailer_offset = pack_bytes
.len()
.checked_sub(format.raw_len())
.ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
let mut cursor = usize::try_from(offset)
.ok()
.filter(|&value| value < trailer_offset)
.ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
let header = parse_entry_header(pack_bytes, &mut cursor)?;
let base = match header.kind {
PackObjectKind::OfsDelta => Some(DeltaBase::Offset(parse_ofs_delta_base_offset(
pack_bytes,
&mut cursor,
offset,
)?)),
PackObjectKind::RefDelta => {
let hash_len = format.raw_len();
if cursor + hash_len > trailer_offset {
return Err(GitError::InvalidFormat(
"truncated ref-delta base object id".into(),
));
}
let oid = ObjectId::from_raw(format, &pack_bytes[cursor..cursor + hash_len])?;
cursor += hash_len;
Some(DeltaBase::Ref(oid))
}
_ => None,
};
let mut body = Vec::new();
inflate_into(
&pack_bytes[cursor..trailer_offset],
&mut body,
header.size.min(usize::MAX as u64) as usize,
)?;
if body.len() as u64 != header.size {
return Err(GitError::InvalidObject(format!(
"pack object declared {} bytes, decoded {}",
header.size,
body.len()
)));
}
let object = match base {
None => {
let object_type = match header.kind {
PackObjectKind::Commit => ObjectType::Commit,
PackObjectKind::Tree => ObjectType::Tree,
PackObjectKind::Blob => ObjectType::Blob,
PackObjectKind::Tag => ObjectType::Tag,
PackObjectKind::OfsDelta | PackObjectKind::RefDelta => {
return Err(GitError::InvalidFormat(
"delta pack entry decoded without a base".into(),
));
}
};
Arc::new(EncodedObject::new(object_type, body))
}
Some(DeltaBase::Offset(base_offset)) => {
let base = match read_object_at_inner(
pack_bytes,
base_offset,
format,
resolve_ref_base,
resolve_ofs_base,
cache,
) {
Ok(base) => base,
Err(pack_err) => match resolve_ofs_base(base_offset)? {
Some(base) => base,
None => return Err(pack_err),
},
};
let resolved = apply_pack_delta(&base.body, &body)?;
Arc::new(EncodedObject::new(base.object_type, resolved))
}
Some(DeltaBase::Ref(base_oid)) => {
let base = resolve_ref_base(&base_oid)?
.ok_or_else(|| GitError::not_found(format!("ref-delta base object {base_oid}")))?;
let resolved = apply_pack_delta(&base.body, &body)?;
Arc::new(EncodedObject::new(base.object_type, resolved))
}
};
cache.insert(offset, Arc::clone(&object));
Ok(object)
}
pub fn read_object_header_at<F>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
mut resolve_ref_base_type: F,
) -> Result<(ObjectType, u64)>
where
F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
{
read_object_header_at_inner(
pack_bytes,
offset,
format,
&mut resolve_ref_base_type,
&mut NoopHeaderTypeCache,
)
}
pub trait HeaderTypeCache {
fn get(&self, pack_offset: u64) -> Option<(ObjectType, u64)>;
fn put(&mut self, pack_offset: u64, header: (ObjectType, u64));
}
pub(crate) struct NoopHeaderTypeCache;
impl HeaderTypeCache for NoopHeaderTypeCache {
fn get(&self, _pack_offset: u64) -> Option<(ObjectType, u64)> {
None
}
fn put(&mut self, _pack_offset: u64, _header: (ObjectType, u64)) {}
}
pub fn read_object_header_at_with_cache<F, C>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
mut resolve_ref_base_type: F,
type_cache: &mut C,
) -> Result<(ObjectType, u64)>
where
F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
C: HeaderTypeCache + ?Sized,
{
if let Some(header) = type_cache.get(offset) {
return Ok(header);
}
read_object_header_at_inner(
pack_bytes,
offset,
format,
&mut resolve_ref_base_type,
type_cache,
)
}
pub(crate) fn read_object_header_at_inner<F, C>(
pack_bytes: &[u8],
offset: u64,
format: ObjectFormat,
resolve_ref_base_type: &mut F,
type_cache: &mut C,
) -> Result<(ObjectType, u64)>
where
F: FnMut(&ObjectId) -> Result<Option<ObjectType>>,
C: HeaderTypeCache + ?Sized,
{
let trailer_offset = pack_bytes
.len()
.checked_sub(format.raw_len())
.ok_or_else(|| GitError::InvalidFormat("pack smaller than its trailer".into()))?;
let mut cursor = usize::try_from(offset)
.ok()
.filter(|&value| value < trailer_offset)
.ok_or_else(|| GitError::InvalidFormat("pack object offset out of range".into()))?;
let header = parse_entry_header(pack_bytes, &mut cursor)?;
let resolved = match header.kind {
PackObjectKind::Commit => (ObjectType::Commit, header.size),
PackObjectKind::Tree => (ObjectType::Tree, header.size),
PackObjectKind::Blob => (ObjectType::Blob, header.size),
PackObjectKind::Tag => (ObjectType::Tag, header.size),
PackObjectKind::OfsDelta => {
let base_offset = parse_ofs_delta_base_offset(pack_bytes, &mut cursor, offset)?;
let size = delta_result_size_from_stream(&pack_bytes[cursor..trailer_offset])?;
let base_type = match type_cache.get(base_offset) {
Some((base_type, _)) => base_type,
None => {
let (base_type, _) = read_object_header_at_inner(
pack_bytes,
base_offset,
format,
resolve_ref_base_type,
type_cache,
)?;
base_type
}
};
(base_type, size)
}
PackObjectKind::RefDelta => {
let hash_len = format.raw_len();
if cursor + hash_len > trailer_offset {
return Err(GitError::InvalidFormat(
"truncated ref-delta base object id".into(),
));
}
let oid = ObjectId::from_raw(format, &pack_bytes[cursor..cursor + hash_len])?;
cursor += hash_len;
let size = delta_result_size_from_stream(&pack_bytes[cursor..trailer_offset])?;
let base_type = resolve_ref_base_type(&oid)?
.ok_or_else(|| GitError::not_found(format!("ref-delta base object {oid}")))?;
(base_type, size)
}
};
type_cache.put(offset, resolved);
Ok(resolved)
}
pub(crate) const DELTA_HEADER_PREFIX_LEN: usize = 32;
pub(crate) fn delta_result_size_from_stream(compressed: &[u8]) -> Result<u64> {
let mut prefix = Vec::new();
inflate_prefix(compressed, DELTA_HEADER_PREFIX_LEN, &mut prefix)?;
decoded_delta_result_size(&prefix)
}
pub(crate) fn parse_entry_header(bytes: &[u8], offset: &mut usize) -> Result<EntryHeader> {
let first = next_byte(bytes, offset)?;
let mut size = u64::from(first & 0x0f);
let kind = match (first >> 4) & 0x07 {
1 => PackObjectKind::Commit,
2 => PackObjectKind::Tree,
3 => PackObjectKind::Blob,
4 => PackObjectKind::Tag,
6 => PackObjectKind::OfsDelta,
7 => PackObjectKind::RefDelta,
other => {
return Err(GitError::InvalidFormat(format!(
"invalid pack object type {other}"
)));
}
};
let mut shift = 4;
let mut byte = first;
while byte & 0x80 != 0 {
byte = next_byte(bytes, offset)?;
let part = u64::from(byte & 0x7f);
size = size
.checked_add(
part.checked_shl(shift)
.ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?,
)
.ok_or_else(|| GitError::InvalidFormat("pack size overflow".into()))?;
shift += 7;
}
Ok(EntryHeader { kind, size })
}
pub(crate) fn parse_ofs_delta_base_offset(bytes: &[u8], offset: &mut usize, entry_offset: u64) -> Result<u64> {
let mut byte = next_byte(bytes, offset)?;
let mut relative = u64::from(byte & 0x7f);
while byte & 0x80 != 0 {
byte = next_byte(bytes, offset)?;
relative = relative
.checked_add(1)
.and_then(|value| value.checked_shl(7))
.and_then(|value| value.checked_add(u64::from(byte & 0x7f)))
.ok_or_else(|| GitError::InvalidFormat("ofs-delta offset overflow".into()))?;
}
entry_offset
.checked_sub(relative)
.ok_or_else(|| GitError::InvalidFormat("ofs-delta points before pack start".into()))
}
pub(crate) fn resolve_pack_entries<F>(
parsed: Vec<ParsedPackEntry>,
format: ObjectFormat,
external_base: &mut F,
) -> Result<Vec<PackObject>>
where
F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
{
let mut offset_to_index = HashMap::with_capacity(parsed.len());
for (idx, entry) in parsed.iter().enumerate() {
offset_to_index.insert(parsed_entry_offset(entry), idx);
}
let mut resolved = vec![None; parsed.len()];
let mut oid_to_index = HashMap::new();
let mut unresolved = 0usize;
for (idx, entry) in parsed.iter().enumerate() {
match entry {
ParsedPackEntry::Resolved(object) => {
oid_to_index.insert(object.entry.oid, idx);
resolved[idx] = Some(object.clone());
}
ParsedPackEntry::Delta { .. } => unresolved += 1,
}
}
while unresolved != 0 {
let mut progress = false;
for idx in 0..parsed.len() {
if resolved[idx].is_some() {
continue;
}
let ParsedPackEntry::Delta {
base,
compressed_size,
delta_size,
offset,
delta,
} = &parsed[idx]
else {
continue;
};
let Some(base_object) = delta_base_object(
base,
&offset_to_index,
&oid_to_index,
&resolved,
external_base,
)?
else {
continue;
};
let body = apply_pack_delta(base_object.body(), delta)?;
let object = EncodedObject::new(base_object.object_type(), body);
let oid = object.object_id(format)?;
let pack_object = PackObject {
entry: PackEntry {
oid,
compressed_size: *compressed_size,
uncompressed_size: object.body.len() as u64,
offset: *offset,
},
object,
};
if pack_object.entry.uncompressed_size != decoded_delta_result_size(delta)? {
return Err(GitError::InvalidObject(
"resolved delta size does not match delta header".into(),
));
}
if *delta_size != delta.len() as u64 {
return Err(GitError::InvalidObject(format!(
"pack delta declared {delta_size} bytes, decoded {}",
delta.len()
)));
}
oid_to_index.insert(oid, idx);
resolved[idx] = Some(pack_object);
unresolved -= 1;
progress = true;
}
if !progress {
return Err(GitError::Unsupported("unresolved delta base".into()));
}
}
resolved
.into_iter()
.map(|entry| entry.ok_or_else(|| GitError::InvalidFormat("unresolved pack entry".into())))
.collect()
}
pub(crate) fn parsed_entry_offset(entry: &ParsedPackEntry) -> u64 {
match entry {
ParsedPackEntry::Resolved(object) => object.entry.offset,
ParsedPackEntry::Delta { offset, .. } => *offset,
}
}
pub(crate) enum DeltaBaseObject<'a> {
Borrowed(&'a EncodedObject),
Owned(EncodedObject),
}
impl DeltaBaseObject<'_> {
pub(crate) fn object_type(&self) -> ObjectType {
match self {
Self::Borrowed(object) => object.object_type,
Self::Owned(object) => object.object_type,
}
}
pub(crate) fn body(&self) -> &[u8] {
match self {
Self::Borrowed(object) => &object.body,
Self::Owned(object) => &object.body,
}
}
}
pub(crate) fn delta_base_object<'a, F>(
base: &DeltaBase,
offset_to_index: &HashMap<u64, usize>,
oid_to_index: &HashMap<ObjectId, usize>,
resolved: &'a [Option<PackObject>],
external_base: &mut F,
) -> Result<Option<DeltaBaseObject<'a>>>
where
F: FnMut(&ObjectId) -> Result<Option<EncodedObject>>,
{
match base {
DeltaBase::Offset(offset) => {
let Some(index) = offset_to_index.get(offset).copied() else {
return Err(GitError::InvalidFormat(format!(
"ofs-delta base offset {offset} not found"
)));
};
Ok(resolved[index]
.as_ref()
.map(|object| DeltaBaseObject::Borrowed(&object.object)))
}
DeltaBase::Ref(oid) => {
if let Some(index) = oid_to_index.get(oid).copied() {
return Ok(resolved[index]
.as_ref()
.map(|object| DeltaBaseObject::Borrowed(&object.object)));
}
external_base(oid).map(|object| object.map(DeltaBaseObject::Owned))
}
}
}
pub(crate) fn apply_pack_delta(base: &[u8], delta: &[u8]) -> Result<Vec<u8>> {
let mut cursor = 0usize;
let base_size = read_delta_varint(delta, &mut cursor)?;
if base_size != base.len() as u64 {
return Err(GitError::InvalidObject(format!(
"delta base size mismatch: expected {base_size}, got {}",
base.len()
)));
}
let result_size = read_delta_varint(delta, &mut cursor)?;
let result_size_hint = usize::try_from(result_size).unwrap_or(usize::MAX);
let mut result = Vec::with_capacity(inflate::bounded_inflate_reserve(
result_size_hint,
delta.len(),
));
while cursor < delta.len() {
let command = delta[cursor];
cursor += 1;
if command & 0x80 != 0 {
let copy_offset =
read_delta_copy_value(delta, &mut cursor, command, &[0x01, 0x02, 0x04, 0x08])?;
let mut copy_size =
read_delta_copy_value(delta, &mut cursor, command, &[0x10, 0x20, 0x40])?;
if copy_size == 0 {
copy_size = 0x10000;
}
let start = usize::try_from(copy_offset)
.map_err(|_| GitError::InvalidObject("delta copy offset overflows usize".into()))?;
let len = usize::try_from(copy_size)
.map_err(|_| GitError::InvalidObject("delta copy size overflows usize".into()))?;
let end = start
.checked_add(len)
.ok_or_else(|| GitError::InvalidObject("delta copy range overflow".into()))?;
let Some(slice) = base.get(start..end) else {
return Err(GitError::InvalidObject(
"delta copy range exceeds base object".into(),
));
};
result.extend_from_slice(slice);
} else if command != 0 {
let len = usize::from(command);
let end = cursor
.checked_add(len)
.ok_or_else(|| GitError::InvalidObject("delta insert range overflow".into()))?;
let Some(slice) = delta.get(cursor..end) else {
return Err(GitError::InvalidObject(
"delta insert range exceeds delta data".into(),
));
};
result.extend_from_slice(slice);
cursor = end;
} else {
return Err(GitError::InvalidObject(
"delta contains reserved zero command".into(),
));
}
}
if result.len() as u64 != result_size {
return Err(GitError::InvalidObject(format!(
"delta result size mismatch: expected {result_size}, got {}",
result.len()
)));
}
Ok(result)
}
pub(crate) fn decoded_delta_result_size(delta: &[u8]) -> Result<u64> {
let mut cursor = 0usize;
let _ = read_delta_varint(delta, &mut cursor)?;
read_delta_varint(delta, &mut cursor)
}
pub(crate) fn read_delta_varint(delta: &[u8], cursor: &mut usize) -> Result<u64> {
let mut value = 0u64;
let mut shift = 0u32;
loop {
let Some(byte) = delta.get(*cursor).copied() else {
return Err(GitError::InvalidObject("truncated delta size".into()));
};
*cursor += 1;
value = value
.checked_add(
u64::from(byte & 0x7f)
.checked_shl(shift)
.ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?,
)
.ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
if byte & 0x80 == 0 {
return Ok(value);
}
shift = shift
.checked_add(7)
.ok_or_else(|| GitError::InvalidObject("delta size overflow".into()))?;
}
}
pub(crate) fn read_delta_copy_value(
delta: &[u8],
cursor: &mut usize,
command: u8,
masks: &[u8],
) -> Result<u64> {
let mut value = 0u64;
for (shift, mask) in masks.iter().enumerate() {
if command & mask != 0 {
let Some(byte) = delta.get(*cursor).copied() else {
return Err(GitError::InvalidObject(
"truncated delta copy command".into(),
));
};
*cursor += 1;
value |= u64::from(byte) << (shift * 8);
}
}
Ok(value)
}