use crate::index_layout::ObjType;
use crate::object::{canonical, GitHashKind, GitObjectKind};
use crate::serve::resolved_type;
pub(crate) enum Item {
Whole(GitObjectKind, Vec<u8>),
OfsDelta { base: usize, body: Vec<u8> },
}
impl Item {
pub(crate) fn body(&self) -> &[u8] {
match self {
Item::Whole(_, b) | Item::OfsDelta { body: b, .. } => b,
}
}
}
pub(crate) fn deflate(bytes: &[u8]) -> Vec<u8> {
use std::io::Write;
let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
e.write_all(bytes).unwrap();
e.finish().unwrap()
}
fn delta_varint(out: &mut Vec<u8>, mut n: u64) {
loop {
let mut b = (n & 0x7f) as u8;
n >>= 7;
if n > 0 {
b |= 0x80;
}
out.push(b);
if n == 0 {
return;
}
}
}
pub(crate) fn insert_only_delta(base_len: usize, target: &[u8]) -> Vec<u8> {
let mut d = Vec::new();
delta_varint(&mut d, base_len as u64);
delta_varint(&mut d, target.len() as u64);
for chunk in target.chunks(0x7f) {
d.push(chunk.len() as u8);
d.extend_from_slice(chunk);
}
d
}
pub(crate) fn build_pack(items: &[Item]) -> Vec<u8> {
let mut pack = b"PACK".to_vec();
pack.extend_from_slice(&2u32.to_be_bytes());
pack.extend_from_slice(&(items.len() as u32).to_be_bytes());
let mut offsets: Vec<u64> = Vec::with_capacity(items.len());
for (i, item) in items.iter().enumerate() {
offsets.push(pack.len() as u64);
match item {
Item::Whole(kind, body) => {
crate::pack_walk::encode_type_and_size(
&mut pack,
resolved_type(*kind),
body.len() as u64,
);
pack.extend_from_slice(&deflate(body));
}
Item::OfsDelta { base, body } => {
assert!(*base < i, "an OFS_DELTA names a base BEHIND it in the pack");
let delta = insert_only_delta(items[*base].body().len(), body);
crate::pack_walk::encode_type_and_size(
&mut pack,
ObjType::OfsDelta,
delta.len() as u64,
);
crate::pack_walk::encode_ofs_distance(&mut pack, offsets[i] - offsets[*base]);
pack.extend_from_slice(&deflate(&delta));
}
}
}
let trailer = GitHashKind::Sha1.oid_of(&pack);
pack.extend_from_slice(&trailer);
pack
}
pub(crate) fn thin_ref_delta_pack(base_oid: &[u8], base_size: u64, body: &[u8]) -> Vec<u8> {
let delta = insert_only_delta(base_size as usize, body);
let mut pack = b"PACK".to_vec();
pack.extend_from_slice(&2u32.to_be_bytes());
pack.extend_from_slice(&1u32.to_be_bytes());
crate::pack_walk::encode_type_and_size(&mut pack, ObjType::RefDelta, delta.len() as u64);
pack.extend_from_slice(base_oid);
pack.extend_from_slice(&deflate(&delta));
let trailer = GitHashKind::Sha1.oid_of(&pack);
pack.extend_from_slice(&trailer);
pack
}
pub(crate) fn whole_object_pack(objects: &[(GitObjectKind, Vec<u8>)]) -> Vec<u8> {
let items: Vec<Item> = objects
.iter()
.map(|(k, b)| Item::Whole(*k, b.clone()))
.collect();
build_pack(&items)
}
const FILES: usize = 128;
const COMMITS: usize = 48;
const CHANGED: usize = 24;
const BLOB_BYTES: usize = 2048;
fn splitmix(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
fn blob_body(file: usize, version: usize) -> Vec<u8> {
let mut s = (file as u64) << 32 | version as u64;
let mut out = Vec::with_capacity(BLOB_BYTES);
while out.len() < BLOB_BYTES {
out.extend_from_slice(&splitmix(&mut s).to_le_bytes());
}
out.truncate(BLOB_BYTES);
out
}
pub(crate) struct Corpus {
pub(crate) pack: Vec<u8>,
pub(crate) commits: Vec<Vec<u8>>,
}
pub(crate) fn corpus() -> &'static Corpus {
static ONCE: std::sync::OnceLock<Corpus> = std::sync::OnceLock::new();
ONCE.get_or_init(build)
}
fn build() -> Corpus {
let hash = GitHashKind::Sha1;
let mut items: Vec<Item> = Vec::new();
let mut current: Vec<(Vec<u8>, usize)> = Vec::with_capacity(FILES);
let mut first: Vec<usize> = Vec::with_capacity(FILES);
let mut version = vec![0usize; FILES];
let mut commits: Vec<Vec<u8>> = Vec::with_capacity(COMMITS);
let mut prev_tree: Option<(Vec<u8>, usize)> = None;
let mut prev_commit: Option<(Vec<u8>, usize)> = None;
for c in 0..COMMITS {
let touched: Vec<usize> = if c == 0 {
(0..FILES).collect()
} else {
(0..CHANGED).map(|k| (c * CHANGED + k) % FILES).collect()
};
for f in touched {
let body = blob_body(f, version[f]);
let oid = hash.oid_of(&canonical(GitObjectKind::Blob, &body));
let at = items.len();
if version[f] == 0 {
items.push(Item::Whole(GitObjectKind::Blob, body));
first.push(at);
current.push((oid, at));
} else {
let base = if f % 2 == 0 { first[f] } else { current[f].1 };
items.push(Item::OfsDelta { base, body });
current[f] = (oid, at);
}
version[f] += 1;
}
let mut payload = Vec::new();
for (f, (oid, _)) in current.iter().enumerate() {
payload.extend_from_slice(format!("100644 f{f:03}.bin\0").as_bytes());
payload.extend_from_slice(oid);
}
let tree_oid = hash.oid_of(&canonical(GitObjectKind::Tree, &payload));
let tree_at = items.len();
match &prev_tree {
Some((_, base)) => items.push(Item::OfsDelta {
base: *base,
body: payload,
}),
None => items.push(Item::Whole(GitObjectKind::Tree, payload)),
}
prev_tree = Some((tree_oid.clone(), tree_at));
let mut body = format!("tree {}\n", hex::encode(&tree_oid));
if let Some((parent, _)) = &prev_commit {
body.push_str(&format!("parent {}\n", hex::encode(parent)));
}
body.push_str(
"author A U Thor <author@example.com> 1700000000 +0000\n\
committer A U Thor <author@example.com> 1700000000 +0000\n\n",
);
body.push_str(&format!("commit {c} of the generated corpus\n"));
let body = body.into_bytes();
let commit_oid = hash.oid_of(&canonical(GitObjectKind::Commit, &body));
let commit_at = items.len();
match &prev_commit {
Some((_, base)) => items.push(Item::OfsDelta {
base: *base,
body,
}),
None => items.push(Item::Whole(GitObjectKind::Commit, body)),
}
prev_commit = Some((commit_oid.clone(), commit_at));
commits.push(commit_oid);
}
Corpus {
pack: build_pack(&items),
commits,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::resolve::{resolve, NoBases};
#[test]
fn the_generated_corpus_has_the_shape_its_consumers_require() {
let c = corpus();
let rows = resolve(&c.pack, GitHashKind::Sha1, 0, &NoBases)
.expect("the corpus resolves standalone ā it is not thin");
assert_eq!(
rows.len(),
FILES + (COMMITS - 1) * CHANGED + 2 * COMMITS,
"the object count is the arithmetic of the four constants"
);
assert!(rows.len() > 100, "consumers assert `all.len() > 100`");
let deltas = rows
.iter()
.filter(|r| matches!(r.stored_type, ObjType::OfsDelta))
.count();
assert_eq!(
deltas,
(COMMITS - 1) * CHANGED + 2 * (COMMITS - 1),
"every version after the first, of a blob, a tree or a commit"
);
assert_eq!(
rows.iter()
.filter(|r| matches!(r.stored_type, ObjType::RefDelta))
.count(),
0,
"a clone pack carries no REF_DELTA, and three guards assert that"
);
for want in [GitObjectKind::Blob, GitObjectKind::Tree, GitObjectKind::Commit] {
assert!(
rows.iter()
.any(|r| r.stored_type == ObjType::OfsDelta && r.kind == want),
"no delta resolves to {want:?}"
);
}
let emitted: u64 = rows.iter().map(|r| r.len).sum();
assert!(
emitted > 1_000_000,
"the zero-copy guard needs a corpus over 1 MB; this one emits {emitted}"
);
assert!(
rows.len() > 1000,
"the gatling fan-out guard needs over 1000 entries to spread over workers; this \
corpus holds {}",
rows.len()
);
let mut uses: std::collections::HashMap<u64, usize> = Default::default();
for r in rows.iter().filter(|r| r.delta_base != 0) {
*uses.entry(r.delta_base).or_default() += 1;
}
let widest = uses.values().copied().max().unwrap_or(0);
assert!(
widest > 2,
"the widest delta fan-out is {widest}; the reference-counting guard needs above 2"
);
assert!(
uses.values().any(|n| *n == 1),
"every base is shared, so the chain half of the delta shapes is gone"
);
let held: std::collections::HashSet<&[u8]> =
rows.iter().map(|r| r.oid.as_slice()).collect();
assert_eq!(held.len(), rows.len(), "two objects share an oid");
assert_eq!(c.commits.len(), COMMITS);
for oid in &c.commits {
assert!(held.contains(oid.as_slice()), "a commit is not in its pack");
}
assert_eq!(c.pack, build().pack, "the corpus is not deterministic");
}
}