crates/gunnar-pack/tests/real_gix_odb.rs
//! The seam test: the writer must serve a pack out of a **real** object database, not
//! only the tidy in-memory fixture the other tests use.
//!
//! `gix::Repository::objects` is a `gix_object::Find + Exists` implementation over
//! loose objects on disk. If `gunnar-pack` composes with that unchanged, it composes
//! with `gunnar-store`'s adapter for the same traits (TOTAL-PROMPT §4). That is the
//! whole reason the read seam is gix's traits and not a bespoke one.
//!
//! `gix` porcelain appears **here only**, as a dev-dependency fixture. This crate is
//! on the server side of the dependency guard and must never link it for real.
mod common;
use std::io::Cursor;
use common::{read_from_pack_dir, PackDir};
use gix_hash::Kind;
use gunnar_pack::{index_pack, write_pack_to_vec, IndexOptions, WriteOptions};
#[test]
fn a_pack_served_from_a_real_gix_object_database_indexes_and_reads_back() -> anyhow::Result<()> {
let repo_dir = tempfile::tempdir()?;
let repo = gix::init_bare(repo_dir.path())?;
assert_eq!(
repo.object_hash(),
Kind::Sha1,
"gix::init_bare makes a sha1 repo"
);
let readme = repo
.write_blob(b"# served straight out of a real odb\n")?
.detach();
let script = repo.write_blob(b"#!/bin/sh\necho gunnar\n")?.detach();
let mut tree = gix_object::Tree {
entries: vec![
gix_object::tree::Entry {
mode: gix_object::tree::EntryKind::Blob.into(),
filename: "README.md".into(),
oid: readme,
},
gix_object::tree::Entry {
mode: gix_object::tree::EntryKind::BlobExecutable.into(),
filename: "run.sh".into(),
oid: script,
},
],
};
tree.entries.sort();
let tree_id = repo.write_object(&tree)?.detach();
let signature = gix::actor::Signature {
name: "Gunnar Test".into(),
email: "gunnar@example.invalid".into(),
time: gix::date::Time::new(1_700_000_000, 0),
};
let commit = gix_object::Commit {
tree: tree_id,
parents: Default::default(),
author: signature.clone(),
committer: signature,
encoding: None,
message: "a real commit\n".into(),
extra_headers: Vec::new(),
};
let commit_id = repo.write_object(&commit)?.detach();
// The seam: `repo.objects` is handed in as nothing but `Find + Exists`.
let ids = vec![commit_id, tree_id, readme, script];
let (pack, outcome) =
write_pack_to_vec(&ids, &repo.objects, &WriteOptions::new(repo.object_hash()))?;
assert_eq!(outcome.num_objects, 4);
let dir = PackDir::new();
let indexed = index_pack(
&mut Cursor::new(&pack),
Some(&dir.pack_dir()),
&IndexOptions::new(Kind::Sha1),
None,
)?;
assert_eq!(indexed.num_objects, 4);
assert_eq!(indexed.pack_id, outcome.pack_id);
// Byte-exactness is asserted against what the *repository* holds, read back out of
// the pack by gix's own object database.
let mut expected = Vec::new();
for id in &ids {
expected.clear();
let from_repo = {
use gix_object::Find;
repo.objects
.try_find(id, &mut expected)
.map_err(|err| anyhow::anyhow!("{err}"))?
.map(|data| (data.kind, data.data.to_vec()))
.expect("the repository has it")
};
let from_pack =
read_from_pack_dir(&dir.objects_dir(), Kind::Sha1, id).expect("the pack has it");
assert_eq!(
from_pack, from_repo,
"object {id} must survive the pack byte-exact"
);
}
Ok(())
}