#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
use sley_core::{GitError, MissingObjectContext, ObjectFormat, ObjectId, Result};
use sley_object::{EncodedObject, ObjectType};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
mod install;
mod loose;
mod pack;
mod reachability;
mod registry;
mod repack;
pub use install::*;
pub use loose::*;
pub use pack::*;
pub use reachability::*;
pub use registry::*;
pub use repack::*;
static TEMPFILE_COUNTER: AtomicU64 = AtomicU64::new(0);
pub trait ObjectReader {
fn read_object(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>>;
fn is_shallow_graft(&self, _oid: &ObjectId) -> bool {
false
}
fn has_shallow_grafts(&self) -> bool {
false
}
fn is_promised_object(&self, _oid: &ObjectId) -> bool {
false
}
}
pub trait ObjectWriter {
fn write_object(&self, object: EncodedObject) -> Result<ObjectId>;
}
pub(crate) fn implied_empty_tree_object(format: ObjectFormat, oid: &ObjectId) -> Option<Arc<EncodedObject>> {
(*oid == ObjectId::empty_tree(format)).then(|| Arc::new(EncodedObject::new(ObjectType::Tree, Vec::new())))
}
pub(crate) fn with_missing_object_context(err: GitError, oid: ObjectId, context: MissingObjectContext) -> GitError {
let kind = err.not_found_kind().and_then(sley_core::NotFoundKind::missing_object_kind);
match kind { Some(kind) => GitError::object_kind_not_found_in(oid, kind, context), None => err }
}
pub fn grafted_parents<R: ObjectReader + ?Sized>(
reader: &R,
oid: &ObjectId,
parents: Vec<ObjectId>,
) -> Vec<ObjectId> {
if reader.is_shallow_graft(oid) {
Vec::new()
} else {
parents
}
}
pub(crate) fn unique_temp_path(parent: &Path) -> PathBuf {
let id = TEMPFILE_COUNTER.fetch_add(1, Ordering::Relaxed);
parent.join(format!("tmp_obj_{}_{}", std::process::id(), id))
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{HashMap, HashSet};
use std::io::Read;
use std::fs;
use sley_core::BString;
use sley_formats::{Bundle, BundleReference, RepositoryLayout};
use sley_object::{Commit, EncodedObject, ObjectType, Tag, Tree, TreeEntry};
use sley_pack::{MultiPackIndex, PackFile, PackIndex, PackWrite, PackWriteOptions};
fn blob_of(byte: u8, len: usize) -> EncodedObject {
EncodedObject::new(ObjectType::Blob, vec![byte; len])
}
fn cached_blob_of(byte: u8, len: usize) -> Arc<EncodedObject> {
Arc::new(blob_of(byte, len))
}
fn read_object_for_assert(reader: &impl ObjectReader, oid: &ObjectId) -> EncodedObject {
reader
.read_object(oid)
.expect("test operation should succeed")
.as_ref()
.clone()
}
#[test]
fn lru_cache_evicts_by_byte_budget_least_recently_used_first() {
let one = cached_object_cost(&blob_of(0, 1000));
let mut cache = LruCache::<u32>::new(one * 2 + 8);
cache.put(1, cached_blob_of(b'a', 1000));
cache.put(2, cached_blob_of(b'b', 1000));
assert!(cache.get(&1).is_some());
cache.put(3, cached_blob_of(b'c', 1000));
assert!(cache.get(&1).is_some());
assert!(cache.get(&2).is_none());
assert!(cache.get(&3).is_some());
}
#[test]
fn lru_cache_zero_budget_is_inert() {
let mut cache = LruCache::<u32>::new(0);
cache.put(1, cached_blob_of(b'a', 16));
assert!(cache.get(&1).is_none());
}
#[test]
fn lru_cache_skips_object_larger_than_budget_and_clears_stale_entry() {
let mut cache = LruCache::<u32>::new(cached_object_cost(&blob_of(0, 100)));
cache.put(1, cached_blob_of(b'a', 50));
assert!(cache.get(&1).is_some());
cache.put(1, cached_blob_of(b'b', 10_000));
assert!(cache.get(&1).is_none());
cache.put(2, cached_blob_of(b'c', 50));
assert!(cache.get(&2).is_some());
}
#[test]
fn lru_cache_replacing_entry_updates_byte_accounting() {
let small = cached_object_cost(&blob_of(0, 500));
let mut cache = LruCache::<u32>::new(small * 2 + 200);
cache.put(1, cached_blob_of(b'a', 500));
cache.put(2, cached_blob_of(b'b', 500));
assert!(cache.get(&1).is_some());
assert!(cache.get(&2).is_some());
cache.put(2, cached_blob_of(b'b', 1000));
assert!(cache.get(&2).is_some());
assert!(cache.get(&1).is_none());
}
#[test]
fn write_and_validate_blob() {
let db = ObjectDatabase::new(ObjectFormat::Sha1);
let oid = db
.write_object(EncodedObject::new(ObjectType::Blob, b"hello\n".to_vec()))
.expect("test operation should succeed");
assert_eq!(oid.to_hex(), "ce013625030ba8dba906f756967f9e9ca394464a");
db.validate(&oid).expect("test operation should succeed");
}
#[test]
fn loose_store_writes_and_reads_object() {
let root = std::env::temp_dir().join(format!(
"sley-loose-store-{}-{}",
std::process::id(),
TEMPFILE_COUNTER.fetch_add(1, Ordering::Relaxed)
));
let store = LooseObjectStore::new(root.join("objects"), ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"hello\n".to_vec());
let oid = store
.write_object(object.clone())
.expect("test operation should succeed");
assert_eq!(read_object_for_assert(&store, &oid), object);
assert!(
store
.object_path(&oid)
.expect("test operation should succeed")
.exists()
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn read_header_detects_corruption_within_gits_header_window() {
let root = temp_root("sley-loose-header-corrupt");
let store = LooseObjectStore::new(root.join("objects"), ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"content\n".to_vec());
let oid = store
.write_object(object)
.expect("test operation should succeed");
let path = store
.object_path(&oid)
.expect("test operation should succeed");
let mut bytes = fs::read(&path).expect("test operation should succeed");
bytes[10] = 0;
fs::write(&path, &bytes).expect("test operation should succeed");
store.invalidate_cache();
let err = store
.read_header(&oid)
.expect_err("corrupt loose header must fail like git's ULHR_BAD");
let msg = err.to_string();
assert!(
msg.contains("unable to unpack") && msg.contains(&oid.to_hex()),
"expected git's ULHR_BAD message, got: {msg}"
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn read_header_ignores_corruption_past_gits_header_window() {
let root = temp_root("sley-loose-header-deep-corrupt");
let store = LooseObjectStore::new(root.join("objects"), ObjectFormat::Sha1);
let body: Vec<u8> = (0..4096u32)
.map(|i| (i.wrapping_mul(2654435761)) as u8)
.collect();
let object = EncodedObject::new(ObjectType::Blob, body.clone());
let oid = store
.write_object(object)
.expect("test operation should succeed");
let path = store
.object_path(&oid)
.expect("test operation should succeed");
let mut bytes = fs::read(&path).expect("test operation should succeed");
let deep = bytes.len() / 2;
bytes[deep] ^= 0xff;
fs::write(&path, &bytes).expect("test operation should succeed");
store.invalidate_cache();
let header = store
.read_header(&oid)
.expect("header-only read must still succeed for deep body corruption");
assert_eq!(header, Some((ObjectType::Blob, body.len() as u64)));
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_reads_object_from_pack_index() {
let root = temp_root("sley-file-odb-pack");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"packed\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
let pack_name = written.checksum.to_hex();
fs::write(
pack_dir.join(format!("pack-{pack_name}.pack")),
written.pack,
)
.expect("test operation should succeed");
fs::write(
pack_dir.join(format!("pack-{pack_name}.idx")),
written.index,
)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
assert!(db.contains(&oid).expect("test operation should succeed"));
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_loose_cache_observes_same_process_write_after_miss() {
let root = temp_root("sley-file-odb-loose-cache-write");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"written after miss\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
assert!(matches!(db.read_object(&oid), Err(GitError::NotFound(_))));
db.loose()
.write_object(object.clone())
.expect("test operation should succeed");
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn present_loose_fanouts_lists_only_existing_two_hex_dirs() {
let root = temp_root("sley-present-fanouts");
let objects = root.join("objects");
fs::create_dir_all(objects.join("ab")).expect("test operation should succeed");
fs::create_dir_all(objects.join("0f")).expect("test operation should succeed");
fs::create_dir_all(objects.join("pack")).expect("test operation should succeed");
fs::create_dir_all(objects.join("info")).expect("test operation should succeed");
fs::create_dir_all(objects.join("zz")).expect("test operation should succeed");
fs::write(objects.join("ff"), b"not a dir").expect("test operation should succeed");
let present = present_loose_fanouts(&objects).expect("test operation should succeed");
assert_eq!(
present,
HashSet::from([0xab, 0x0f]),
"only the genuine two-hex fanout directories should be reported"
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn present_loose_fanouts_empty_when_objects_dir_absent() {
let root = temp_root("sley-present-fanouts-absent");
fs::create_dir_all(&root).expect("test operation should succeed");
let present =
present_loose_fanouts(&root.join("objects")).expect("test operation should succeed");
assert!(present.is_empty());
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn packed_only_repo_read_does_not_probe_loose_fanout_dirs() {
let root = temp_root("sley-packed-no-loose-probe");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"packed only\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
let pack_name = written.checksum.to_hex();
fs::write(
pack_dir.join(format!("pack-{pack_name}.pack")),
written.pack,
)
.expect("test operation should succeed");
fs::write(
pack_dir.join(format!("pack-{pack_name}.idx")),
written.index,
)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
assert_eq!(
db.read_object_header(&oid)
.expect("test operation should succeed"),
Some((ObjectType::Blob, object.body.len() as u64))
);
assert_eq!(read_object_for_assert(&db, &oid), object);
let fanout_hex = format!("{:02x}", oid.as_bytes()[0]);
assert!(
!git_dir.join("objects").join(&fanout_hex).exists(),
"reading a packed object must not create its loose fanout dir"
);
if let Ok(guard) = db.loose().loose_cache.lock() {
assert_eq!(
guard.present_fanouts.as_ref(),
Some(&HashSet::new()),
"an all-packed repo must learn zero present fanouts"
);
}
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn loose_object_in_existing_fanout_still_resolves_through_cache() {
let root = temp_root("sley-loose-resolves");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"a genuine loose object\n".to_vec());
let oid = db
.write_object(object.clone())
.expect("test operation should succeed");
db.refresh_read_cache();
assert_eq!(
db.read_object_header(&oid)
.expect("test operation should succeed"),
Some((ObjectType::Blob, object.body.len() as u64))
);
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn loose_write_into_previously_absent_fanout_is_found_after_cache_built() {
let root = temp_root("sley-new-loose-after-cache");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let packed = EncodedObject::new(ObjectType::Blob, b"seed packed\n".to_vec());
let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&packed))
.expect("test operation should succeed");
let pack_name = written.checksum.to_hex();
fs::write(
pack_dir.join(format!("pack-{pack_name}.pack")),
written.pack,
)
.expect("test operation should succeed");
fs::write(
pack_dir.join(format!("pack-{pack_name}.idx")),
written.index,
)
.expect("test operation should succeed");
let packed_oid = packed
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
assert_eq!(read_object_for_assert(&db, &packed_oid), packed);
let loose = EncodedObject::new(ObjectType::Blob, b"new loose into empty fanout\n".to_vec());
let loose_oid = db
.write_object(loose.clone())
.expect("test operation should succeed");
assert_eq!(
db.read_object_header(&loose_oid)
.expect("test operation should succeed"),
Some((ObjectType::Blob, loose.body.len() as u64)),
"a loose object written after the present-fanout cache was built must be found"
);
assert_eq!(read_object_for_assert(&db, &loose_oid), loose);
assert_eq!(read_object_for_assert(&db, &packed_oid), packed);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn loose_copy_is_consulted_when_packed_copy_is_corrupt() {
let root = temp_root("sley-loose-shadows-corrupt-pack");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"shadow me\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
let pack_name = written.checksum.to_hex();
let pack_path = pack_dir.join(format!("pack-{pack_name}.pack"));
fs::write(&pack_path, written.pack).expect("test operation should succeed");
fs::write(
pack_dir.join(format!("pack-{pack_name}.idx")),
written.index,
)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
db.loose()
.write_object(object.clone())
.expect("test operation should succeed");
let mut pack_bytes = fs::read(&pack_path).expect("test operation should succeed");
let mid = pack_bytes.len() / 2;
pack_bytes[mid] ^= 0xff;
fs::write(&pack_path, &pack_bytes).expect("test operation should succeed");
db.refresh_read_cache();
assert_eq!(
read_object_for_assert(&db, &oid),
object,
"a good loose copy must shadow a corrupt packed copy"
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn loose_ofs_delta_base_recovers_child_from_corrupt_pack_base() {
let root = temp_root("sley-ofs-delta-base-corrupt-loose-recovery");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let mut base_body = Vec::new();
for idx in 0..256u16 {
base_body.extend_from_slice(format!("shared line {idx:03}\n").as_bytes());
}
let base = EncodedObject::new(ObjectType::Blob, base_body.clone());
let mut first_body = base_body;
first_body.extend_from_slice(b"first delta payload\n");
let first = EncodedObject::new(ObjectType::Blob, first_body.clone());
let mut second_body = first_body;
second_body.extend_from_slice(b"second delta payload\n");
let second = EncodedObject::new(ObjectType::Blob, second_body);
let base_oid = base
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let first_oid = first
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let second_oid = second
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let options = PackWriteOptions::new()
.with_prefer_ofs_delta(true)
.with_reorder(false);
let written = PackFile::write_packed_with_options(
&[base, first.clone(), second.clone()],
ObjectFormat::Sha1,
&options,
)
.expect("test operation should succeed");
let stats = PackFile::verify_pack_stats(&written.pack, ObjectFormat::Sha1)
.expect("test operation should succeed");
let first_stat = stats
.objects
.iter()
.find(|stat| stat.oid == first_oid)
.expect("first object should be packed");
let second_stat = stats
.objects
.iter()
.find(|stat| stat.oid == second_oid)
.expect("second object should be packed");
assert_eq!(first_stat.base_oid, Some(base_oid));
assert_eq!(second_stat.base_oid, Some(first_oid));
let installed = db
.install_pack(&written)
.expect("test operation should succeed");
db.loose()
.write_object(first.clone())
.expect("test operation should succeed");
let mut corrupt_pack = written.pack;
let base_reference = ofs_delta_base_reference_position(&corrupt_pack, first_stat.offset);
corrupt_pack[base_reference] = if corrupt_pack[base_reference] == 1 {
2
} else {
1
};
assert!(PackFile::verify_pack_stats(&corrupt_pack, ObjectFormat::Sha1).is_err());
fs::write(&installed.pack_path, &corrupt_pack).expect("test operation should succeed");
db.refresh_read_cache();
assert_eq!(read_object_for_assert(&db, &first_oid), first);
assert_eq!(read_object_for_assert(&db, &second_oid), second);
fs::remove_dir_all(root).expect("test operation should succeed");
}
fn ofs_delta_base_reference_position(pack: &[u8], offset: u64) -> usize {
let mut cursor = usize::try_from(offset).expect("test operation should succeed");
let first = pack[cursor];
cursor += 1;
let kind = (first >> 4) & 0x07;
let mut byte = first;
while byte & 0x80 != 0 {
byte = pack[cursor];
cursor += 1;
}
assert_eq!(kind, 6, "expected an ofs-delta entry");
cursor
}
#[test]
fn object_presence_checker_observes_same_process_loose_write_after_miss() {
let root = temp_root("sley-presence-checker-loose-cache-write");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let mut checker = db.presence_checker();
let object = EncodedObject::new(ObjectType::Blob, b"checker loose after miss\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
assert!(
!checker
.contains(&oid)
.expect("test operation should succeed")
);
db.loose()
.write_object(object)
.expect("test operation should succeed");
assert!(
checker
.contains(&oid)
.expect("test operation should succeed")
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn read_object_header_matches_full_read_for_loose_and_packed_and_delta() {
let root = temp_root("sley-read-object-header");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let db = FileObjectDatabase::from_git_dir(&git_dir, format);
let loose = EncodedObject::new(ObjectType::Blob, b"loose header object\n".to_vec());
let loose_oid = db
.write_object(loose.clone())
.expect("test operation should succeed");
let base = EncodedObject::new(ObjectType::Blob, vec![b'a'; 4096]);
let mut child_body = vec![b'a'; 4096];
child_body.extend_from_slice(b" plus a deltified tail\n");
let child = EncodedObject::new(ObjectType::Blob, child_body);
let commitish =
EncodedObject::new(ObjectType::Commit, b"header-only type probe\n".to_vec());
let base_oid = base
.object_id(format)
.expect("test operation should succeed");
let child_oid = child
.object_id(format)
.expect("test operation should succeed");
let commit_oid = commitish
.object_id(format)
.expect("test operation should succeed");
let options = PackWriteOptions::new()
.with_prefer_ofs_delta(true)
.with_reorder(false);
let pack = PackFile::write_packed_with_options(
&[base.clone(), child.clone(), commitish.clone()],
format,
&options,
)
.expect("test operation should succeed");
db.install_pack(&pack)
.expect("test operation should succeed");
for (oid, want_type, want_len) in [
(&loose_oid, ObjectType::Blob, loose.body.len()),
(&base_oid, ObjectType::Blob, base.body.len()),
(&child_oid, ObjectType::Blob, child.body.len()),
(&commit_oid, ObjectType::Commit, commitish.body.len()),
] {
assert_eq!(
db.read_object_header(oid)
.expect("test operation should succeed"),
Some((want_type, want_len as u64)),
"header for {oid}"
);
let full = db.read_object(oid).expect("test operation should succeed");
assert_eq!(
db.read_object_header(oid)
.expect("test operation should succeed"),
Some((full.object_type, full.body.len() as u64))
);
}
let missing = ObjectId::from_hex(format, "0000000000000000000000000000000000000001")
.expect("test operation should succeed");
assert_eq!(
db.read_object_header(&missing)
.expect("test operation should succeed"),
None
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn object_storage_info_reports_loose_packed_and_delta_metadata() {
let root = temp_root("sley-object-storage-info");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let db = FileObjectDatabase::from_git_dir(&git_dir, format);
let loose = EncodedObject::new(ObjectType::Blob, b"loose storage object\n".to_vec());
let loose_oid = db
.write_object(loose)
.expect("test operation should succeed");
let loose_size = fs::metadata(
db.loose()
.object_path(&loose_oid)
.expect("test operation should succeed"),
)
.expect("test operation should succeed")
.len();
let loose_info = db
.object_storage_info(&loose_oid)
.expect("test operation should succeed")
.expect("test operation should succeed");
assert_eq!(loose_info.disk_size, loose_size);
assert_eq!(
loose_info.deltabase,
zero_oid(format).expect("test operation should succeed")
);
let base = EncodedObject::new(ObjectType::Blob, vec![b'a'; 4096]);
let mut child_body = vec![b'a'; 4096];
child_body.extend_from_slice(b" changed tail\n");
let child = EncodedObject::new(ObjectType::Blob, child_body);
let base_oid = base
.object_id(format)
.expect("test operation should succeed");
let child_oid = child
.object_id(format)
.expect("test operation should succeed");
let options = PackWriteOptions::new()
.with_prefer_ofs_delta(true)
.with_reorder(false);
let pack = PackFile::write_packed_with_options(&[base, child], format, &options)
.expect("test operation should succeed");
db.install_pack(&pack)
.expect("test operation should succeed");
let base_info = db
.object_storage_info(&base_oid)
.expect("test operation should succeed")
.expect("test operation should succeed");
assert!(base_info.disk_size > 0);
assert_eq!(
base_info.deltabase,
zero_oid(format).expect("test operation should succeed")
);
let child_info = db
.object_storage_info(&child_oid)
.expect("test operation should succeed")
.expect("test operation should succeed");
assert!(child_info.disk_size > 0);
assert_eq!(child_info.deltabase, base_oid);
let missing = ObjectId::from_hex(format, "0000000000000000000000000000000000000001")
.expect("test operation should succeed");
assert_eq!(
db.object_storage_info(&missing)
.expect("test operation should succeed"),
None
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn object_storage_info_uses_midx_when_pack_sidecar_is_missing() {
let root = temp_root("sley-object-storage-midx-fallback");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let first = EncodedObject::new(ObjectType::Blob, b"first packed object\n".to_vec());
let second = EncodedObject::new(ObjectType::Blob, b"second packed object\n".to_vec());
let written = PackFile::write_undeltified_sha1(&[first, second])
.expect("test operation should succeed");
let pack_name = written.checksum.to_hex();
let pack_path = pack_dir.join(format!("pack-{pack_name}.pack"));
let idx_path = pack_dir.join(format!("pack-{pack_name}.idx"));
fs::write(&pack_path, &written.pack).expect("test operation should succeed");
fs::write(&idx_path, &written.index).expect("test operation should succeed");
let idx_name = idx_path
.file_name()
.and_then(|name| name.to_str())
.expect("test operation should succeed")
.to_string();
let midx_objects = written
.entries
.iter()
.map(|entry| sley_pack::MultiPackIndexEntry {
oid: entry.oid,
pack_int_id: 0,
offset: entry.offset,
force_large_offset: false,
})
.collect::<Vec<_>>();
let midx = MultiPackIndex::write(format, 1, &[idx_name], &midx_objects)
.expect("test operation should succeed");
fs::write(pack_dir.join("multi-pack-index"), midx).expect("test operation should succeed");
let target = written
.entries
.iter()
.min_by_key(|entry| entry.offset)
.expect("test operation should succeed")
.oid;
let indexed_info = FileObjectDatabase::from_git_dir(&git_dir, format)
.object_storage_info(&target)
.expect("test operation should succeed")
.expect("test operation should succeed");
fs::remove_file(&idx_path).expect("test operation should succeed");
let missing_idx_info = FileObjectDatabase::from_git_dir(&git_dir, format)
.object_storage_info(&target)
.expect("test operation should succeed")
.expect("test operation should succeed");
assert_eq!(missing_idx_info, indexed_info);
fs::write(&idx_path, &written.index).expect("test operation should succeed");
fs::remove_file(&pack_path).expect("test operation should succeed");
let missing_pack_info = FileObjectDatabase::from_git_dir(&git_dir, format)
.object_storage_info(&target)
.expect("test operation should succeed")
.expect("test operation should succeed");
assert_eq!(missing_pack_info.disk_size, indexed_info.disk_size);
assert_eq!(
missing_pack_info.deltabase,
zero_oid(format).expect("test operation should succeed")
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_resolves_unique_loose_object_prefix() {
let root = temp_root("sley-file-odb-prefix-loose");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"prefix loose\n".to_vec());
let oid = db
.write_object(object)
.expect("test operation should succeed");
let prefix = &oid.to_hex()[..8];
assert_eq!(
db.resolve_prefix(prefix)
.expect("test operation should succeed"),
ObjectPrefixResolution::Unique(oid)
);
assert!(
db.object_ids()
.expect("test operation should succeed")
.contains(&oid)
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_resolves_unique_packed_object_prefix() {
let root = temp_root("sley-file-odb-prefix-packed");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"prefix packed\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
db.install_pack(&pack)
.expect("test operation should succeed");
let prefix = &oid.to_hex()[..8];
assert_eq!(
db.resolve_prefix(prefix)
.expect("test operation should succeed"),
ObjectPrefixResolution::Unique(oid)
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_reports_ambiguous_object_prefix() {
let root = temp_root("sley-file-odb-prefix-ambiguous");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let mut seen = HashMap::new();
let (prefix, first, second) = (0..10_000)
.find_map(|idx| {
let object =
EncodedObject::new(ObjectType::Blob, format!("ambiguous {idx}\n").into_bytes());
let oid = db
.write_object(object)
.expect("test operation should succeed");
let prefix = oid.to_hex()[..4].to_string();
seen.insert(prefix.clone(), oid)
.map(|first| (prefix, first, oid))
})
.expect("test should find a 4-hex collision");
let ObjectPrefixResolution::Ambiguous(mut matches) = db
.resolve_prefix(&prefix)
.expect("test operation should succeed")
else {
panic!("expected ambiguous prefix {prefix}");
};
matches.sort_by_key(ObjectId::to_hex);
let mut expected = vec![first, second];
expected.sort_by_key(ObjectId::to_hex);
assert_eq!(matches, expected);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_rejects_too_short_object_prefix() {
let root = temp_root("sley-file-odb-prefix-short");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
assert!(matches!(
db.resolve_prefix("abc"),
Err(GitError::InvalidObjectId(_))
));
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_reads_sha256_object_from_pack_index() {
let root = temp_root("sley-file-odb-pack-sha256");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"packed sha256\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha256)
.expect("test operation should succeed");
let written =
PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
.expect("test operation should succeed");
let pack_name = written.checksum.to_hex();
fs::write(
pack_dir.join(format!("pack-{pack_name}.pack")),
written.pack,
)
.expect("test operation should succeed");
fs::write(
pack_dir.join(format!("pack-{pack_name}.idx")),
written.index,
)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha256);
assert!(db.contains(&oid).expect("test operation should succeed"));
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_installs_sha256_pack_without_loose_objects() {
let root = temp_root("sley-file-odb-install-pack");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"installed sha256 pack\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha256)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha256);
let result = db
.install_pack(&pack)
.expect("test operation should succeed");
assert_eq!(result.pack_name, format!("pack-{}", pack.checksum.to_hex()));
assert_eq!(result.object_ids, vec![oid]);
assert!(result.pack_path.exists());
assert!(result.index_path.exists());
assert_eq!(result.promisor_path, None);
assert!(
!db.loose()
.object_path(&oid)
.expect("test operation should succeed")
.exists()
);
assert!(db.contains(&oid).expect("test operation should succeed"));
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_installs_raw_sha256_pack_without_loose_objects() {
let root = temp_root("sley-file-odb-install-raw-pack");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"installed raw sha256 pack\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha256)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha256);
let mut reader = pack.pack.as_slice();
let result = db
.install_raw_pack_from_reader(&mut reader)
.expect("test operation should succeed");
assert_eq!(result.pack_name, format!("pack-{}", pack.checksum.to_hex()));
assert_eq!(result.object_ids, vec![oid]);
assert!(result.pack_path.exists());
assert!(result.index_path.exists());
assert_eq!(result.promisor_path, None);
assert!(
!db.loose()
.object_path(&oid)
.expect("test operation should succeed")
.exists()
);
assert!(db.contains(&oid).expect("test operation should succeed"));
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_streams_raw_pack_install_to_packfile() {
use std::io::Write as _;
let root = temp_root("sley-file-odb-stream-raw-pack");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"streamed raw pack\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha1)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let mut install = db
.begin_raw_pack_install(pack.checksum, pack.pack.len() as u64)
.expect("test operation should succeed");
for chunk in pack.pack.chunks(5) {
install
.write_all(chunk)
.expect("test operation should succeed");
}
let result = install.finish().expect("test operation should succeed");
assert_eq!(result.pack_name, format!("pack-{}", pack.checksum.to_hex()));
assert_eq!(result.object_ids, vec![oid]);
assert_eq!(
fs::read(&result.pack_path).expect("test operation should succeed"),
pack.pack
);
assert!(result.index_path.exists());
assert!(db.contains(&oid).expect("test operation should succeed"));
assert_eq!(read_object_for_assert(&db, &oid), object);
let bad_id = ObjectId::from_raw(ObjectFormat::Sha1, &[0x42; 20])
.expect("test operation should succeed");
let mut bad_install = db
.begin_raw_pack_install(bad_id, pack.pack.len() as u64)
.expect("test operation should succeed");
bad_install
.write_all(&pack.pack)
.expect("test operation should succeed");
assert!(
bad_install.finish().is_err(),
"checksum mismatch should reject the streamed pack"
);
assert!(
!git_dir
.join("objects")
.join("pack")
.join(format!("pack-{}.pack", bad_id.to_hex()))
.exists()
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_installs_unknown_length_raw_pack_from_reader() {
let root = temp_root("sley-file-odb-install-raw-pack-reader");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"reader streamed raw pack\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha1)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let mut reader = pack.pack.as_slice();
let result = db
.install_raw_pack_from_reader(&mut reader)
.expect("test operation should succeed");
assert_eq!(result.pack_name, format!("pack-{}", pack.checksum.to_hex()));
assert_eq!(result.object_ids, vec![oid]);
assert_eq!(
fs::read(&result.pack_path).expect("test operation should succeed"),
pack.pack
);
assert!(result.index_path.exists());
assert!(db.contains(&oid).expect("test operation should succeed"));
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_rejects_raw_pack_stream_exceeding_max_input_size() {
let root = temp_root("sley-file-odb-install-raw-pack-max-size");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"bounded raw pack install\n".to_vec());
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha1)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let pack_dir = git_dir.join("objects").join("pack");
let before = fs::read_dir(&pack_dir)
.map(|entries| entries.count())
.unwrap_or_default();
let mut reader = pack.pack.as_slice();
let limit = 32u64;
let err = db
.install_raw_pack_from_reader_with_options(
&mut reader,
RawPackInstallOptions {
max_input_size: Some(limit),
..Default::default()
},
)
.expect_err("oversized stream should be rejected");
assert!(
err.to_string().contains("pack exceeds maximum allowed size"),
"unexpected error: {err}"
);
let temp_files = fs::read_dir(&pack_dir)
.expect("pack dir should exist")
.filter_map(|entry| entry.ok())
.map(|entry| entry.path())
.filter(|path| {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with("tmp_obj_"))
})
.collect::<Vec<_>>();
assert!(
temp_files.is_empty(),
"temp pack staging file should be removed on failure"
);
let after = fs::read_dir(&pack_dir)
.map(|entries| entries.count())
.unwrap_or_default();
assert_eq!(after, before, "no durable pack files should be installed");
let installed = fs::read_dir(&pack_dir)
.map(|entries| {
entries
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry
.path()
.extension()
.and_then(|ext| ext.to_str())
== Some("pack")
})
.count()
})
.unwrap_or_default();
assert_eq!(installed, 0);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_rejects_unknown_length_raw_pack_with_trailing_bytes() {
let root = temp_root("sley-file-odb-install-raw-pack-reader-trailing");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"trailing streamed raw pack\n".to_vec());
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha1)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let mut bytes = pack.pack;
bytes.extend_from_slice(b"not part of the pack");
let mut reader = bytes.as_slice();
let err = db
.install_raw_pack_from_reader(&mut reader)
.expect_err("trailing bytes should be rejected");
assert!(err.to_string().contains("trailing bytes after checksum"));
let pack_dir = git_dir.join("objects").join("pack");
let pack_entries = fs::read_dir(&pack_dir)
.map(|entries| entries.count())
.unwrap_or_default();
assert_eq!(pack_entries, 0);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_rejects_noncanonical_pack_index() {
let root = temp_root("sley-file-odb-install-bad-index");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"bad index crc\n".to_vec());
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha1)
.expect("test operation should succeed");
let mut entries = pack.entries.clone();
entries[0].crc32 ^= 1;
let mut bad_pack = pack.clone();
bad_pack.index = PackIndex::write_v2(ObjectFormat::Sha1, &entries, &pack.checksum)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
assert!(db.install_pack(&bad_pack).is_err());
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_installs_raw_promisor_pack_with_sidecar() {
let root = temp_root("sley-file-odb-install-raw-promisor-pack");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"installed promisor pack\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha1)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let mut reader = pack.pack.as_slice();
let result = db
.install_raw_pack_from_reader_with_options(
&mut reader,
RawPackInstallOptions {
promisor: true,
..Default::default()
},
)
.expect("test operation should succeed");
let promisor_path = result.promisor_path.expect("promisor sidecar");
assert_eq!(promisor_path.file_stem(), result.pack_path.file_stem());
assert_eq!(
promisor_path.extension().and_then(|ext| ext.to_str()),
Some("promisor")
);
assert!(promisor_path.exists());
assert_eq!(
fs::read(&promisor_path).expect("test operation should succeed"),
b""
);
assert!(result.pack_path.exists());
assert!(result.index_path.exists());
assert!(
!db.loose()
.object_path(&oid)
.expect("test operation should succeed")
.exists()
);
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn repository_objects_dir_uses_linked_worktree_common_dir() {
let root = temp_root("sley-odb-common-dir");
let common = root.join(".git");
let admin = common.join("worktrees").join("linked");
fs::create_dir_all(&admin).expect("test operation should succeed");
fs::write(admin.join("commondir"), "../..\n").expect("test operation should succeed");
let common = fs::canonicalize(common).expect("test operation should succeed");
assert_eq!(repository_common_dir(&admin), common);
assert_eq!(repository_objects_dir(&admin), common.join("objects"));
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn reachable_object_helpers_walk_graph_and_install_pack() {
let root = temp_root("sley-reachable-pack");
let source_git_dir = root.join("source.git");
let destination_git_dir = root.join("destination.git");
fs::create_dir_all(source_git_dir.join("objects")).expect("test operation should succeed");
fs::create_dir_all(destination_git_dir.join("objects"))
.expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let source = FileObjectDatabase::from_git_dir(&source_git_dir, format);
let destination = FileObjectDatabase::from_git_dir(&destination_git_dir, format);
let blob = EncodedObject::new(ObjectType::Blob, b"reachable payload\n".to_vec());
let blob_oid = source
.write_object(blob.clone())
.expect("test operation should succeed");
let tree = EncodedObject::new(
ObjectType::Tree,
Tree {
entries: vec![TreeEntry {
mode: 0o100644,
name: BString::from(b"payload.txt"),
oid: blob_oid,
}],
}
.write(),
);
let tree_oid = source
.write_object(tree.clone())
.expect("test operation should succeed");
let identity = b"Example <example@example.invalid> 0 +0000".to_vec();
let commit = EncodedObject::new(
ObjectType::Commit,
Commit {
tree: tree_oid,
parents: Vec::new(),
author: identity.clone(),
committer: identity,
encoding: None,
message: b"initial\n".to_vec(),
}
.write(),
);
let commit_oid = source
.write_object(commit.clone())
.expect("test operation should succeed");
let reachable = collect_reachable_object_ids(&source, format, std::iter::once(commit_oid))
.expect("test operation should succeed");
assert!(reachable.contains(&commit_oid));
assert!(reachable.contains(&tree_oid));
assert!(reachable.contains(&blob_oid));
let install =
install_reachable_pack(&source, &destination, format, std::iter::once(commit_oid))
.expect("test operation should succeed")
.expect("reachable pack should be written");
assert_eq!(install.object_ids.len(), 3);
for (oid, object) in [
(&commit_oid, &commit),
(&tree_oid, &tree),
(&blob_oid, &blob),
] {
assert!(
!destination
.loose()
.object_path(oid)
.expect("test operation should succeed")
.exists()
);
assert!(
destination
.contains(oid)
.expect("test operation should succeed")
);
assert_eq!(read_object_for_assert(&destination, oid), *object);
}
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn reachable_object_helpers_respect_exclusions_and_duplicate_starts() {
let root = temp_root("sley-reachable-exclusions");
let git_dir = root.join("repo.git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let db = FileObjectDatabase::from_git_dir(&git_dir, format);
let blob = EncodedObject::new(ObjectType::Blob, b"excluded payload\n".to_vec());
let blob_oid = db
.write_object(blob)
.expect("test operation should succeed");
let tree = EncodedObject::new(
ObjectType::Tree,
Tree {
entries: vec![TreeEntry {
mode: 0o100644,
name: BString::from(b"payload.txt"),
oid: blob_oid,
}],
}
.write(),
);
let tree_oid = db
.write_object(tree)
.expect("test operation should succeed");
let identity = b"Example <example@example.invalid> 0 +0000".to_vec();
let commit = EncodedObject::new(
ObjectType::Commit,
Commit {
tree: tree_oid,
parents: Vec::new(),
author: identity.clone(),
committer: identity,
encoding: None,
message: b"initial\n".to_vec(),
}
.write(),
);
let commit_oid = db
.write_object(commit)
.expect("test operation should succeed");
let excluded = HashSet::from([tree_oid]);
let objects = collect_reachable_objects(&db, format, [commit_oid, commit_oid], &excluded)
.expect("test operation should succeed");
assert_eq!(objects.len(), 1);
assert_eq!(
objects[0]
.object_id(format)
.expect("test operation should succeed"),
commit_oid
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn build_reachable_pack_returns_raw_pack_and_respects_empty_exclusions() {
let root = temp_root("sley-build-reachable-pack");
let git_dir = root.join("repo.git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let db = FileObjectDatabase::from_git_dir(&git_dir, format);
let object = EncodedObject::new(ObjectType::Blob, b"raw reachable pack\n".to_vec());
let oid = db
.write_object(object.clone())
.expect("test operation should succeed");
let pack = build_reachable_pack(&db, format, std::iter::once(oid), &HashSet::new())
.expect("test operation should succeed")
.expect("reachable pack should be built");
assert!(pack.pack.starts_with(b"PACK"));
assert_eq!(pack.entries.len(), 1);
assert_eq!(pack.entries[0].oid, oid);
let pack_path = root.join("reachable.pack");
let pack_file = build_reachable_pack_file(
&db,
format,
std::iter::once(oid),
&HashSet::new(),
&pack_path,
)
.expect("test operation should succeed")
.expect("reachable pack file should be built");
assert_eq!(pack_file.checksum, pack.checksum);
assert_eq!(pack_file.pack_size, pack.pack.len() as u64);
assert_eq!(pack_file.object_count, 1);
assert_eq!(
fs::read(&pack_file.pack_path).expect("test operation should succeed"),
pack.pack
);
let mut streamed_pack = Vec::new();
let streamed = write_reachable_pack_to_writer(
&db,
format,
std::iter::once(oid),
&HashSet::new(),
&mut streamed_pack,
)
.expect("test operation should succeed")
.expect("reachable pack should be streamed");
assert_eq!(streamed.checksum, pack.checksum);
assert_eq!(streamed.pack_size, pack.pack.len() as u64);
assert_eq!(streamed.object_count, 1);
assert_eq!(streamed_pack, pack.pack);
let mut sink = std::io::sink();
let dry_run = write_reachable_pack_to_writer(
&db,
format,
std::iter::once(oid),
&HashSet::new(),
&mut sink,
)
.expect("test operation should succeed")
.expect("reachable pack should stream to sink");
assert_eq!(dry_run.checksum, pack.checksum);
assert_eq!(dry_run.pack_size, pack.pack.len() as u64);
assert_eq!(dry_run.object_count, 1);
let excluded = HashSet::from([oid]);
assert!(
build_reachable_pack(
&db,
format,
pack.entries.into_iter().map(|entry| entry.oid),
&excluded
)
.expect("test operation should succeed")
.is_none()
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn large_reachable_pack_streams_objects_by_id_windows() {
let root = temp_root("sley-reachable-pack-streamed-large");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let db = FileObjectDatabase::from_git_dir(&git_dir, format);
let mut roots = Vec::new();
for idx in 0..(REACHABLE_PACK_STREAMING_MIN_OBJECTS + 5) {
let object = EncodedObject::new(
ObjectType::Blob,
format!("streamed reachable blob {idx:04}\n").into_bytes(),
);
roots.push(
db.write_object(object)
.expect("test operation should succeed"),
);
}
let mut pack_bytes = Vec::new();
let summary = write_reachable_pack_to_writer(
&db,
format,
roots.iter().copied(),
&HashSet::new(),
&mut pack_bytes,
)
.expect("test operation should succeed")
.expect("reachable pack should be streamed");
assert_eq!(summary.object_count, roots.len());
assert!(
summary.delta_count > 0,
"streamed large packs should still find deltas"
);
assert_eq!(summary.pack_size, pack_bytes.len() as u64);
let parsed = PackFile::parse(&pack_bytes, format).expect("test operation should succeed");
let expected_oids = roots.iter().copied().collect::<HashSet<_>>();
let parsed_oids = parsed
.entries
.iter()
.map(|entry| entry.entry.oid)
.collect::<HashSet<_>>();
assert_eq!(parsed.checksum, summary.checksum);
assert_eq!(parsed_oids, expected_oids);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn index_raw_pack_returns_validated_pack_metadata() {
let root = temp_root("sley-index-raw-pack");
let git_dir = root.join("repo.git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let mut db = FileObjectDatabase::from_git_dir(&git_dir, format);
let graph = write_commit_graph(&mut db, b"pack indexed\n");
let commit_oid = graph[0].0;
let expected = graph
.iter()
.map(|(oid, object)| (*oid, (object.object_type, object.body.len() as u64)))
.collect::<HashMap<_, _>>();
let pack = build_reachable_pack(&db, format, std::iter::once(commit_oid), &HashSet::new())
.expect("test operation should succeed")
.expect("reachable pack should be built");
let indexed = index_raw_pack(&pack.pack, format).expect("test operation should succeed");
let mut cursor = std::io::Cursor::new(pack.pack.clone());
let streamed = index_raw_pack_from_reader(&mut cursor, format)
.expect("streamed pack indexing should match in-memory indexing");
assert_eq!(streamed, indexed);
let pack_path = root.join("reachable.pack");
fs::write(&pack_path, &pack.pack).expect("test operation should succeed");
let file_indexed = index_raw_pack_file(&pack_path, format)
.expect("file-backed pack indexing should match in-memory indexing");
assert_eq!(file_indexed, indexed);
assert_eq!(indexed.pack_id, pack.checksum);
assert_eq!(indexed.index, pack.index);
assert_eq!(indexed.objects.len(), 3);
for object in indexed.objects {
let (expected_type, expected_size) = expected
.get(&object.oid)
.copied()
.expect("indexed object should be reachable");
assert_eq!(object.object_type, expected_type);
assert_eq!(object.size, expected_size);
assert!(object.offset > 0);
}
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn reachable_object_helpers_follow_tags_and_report_missing_objects() {
let root = temp_root("sley-reachable-tags");
let git_dir = root.join("repo.git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let db = FileObjectDatabase::from_git_dir(&git_dir, format);
let blob = EncodedObject::new(ObjectType::Blob, b"tagged payload\n".to_vec());
let blob_oid = db
.write_object(blob)
.expect("test operation should succeed");
let tag = EncodedObject::new(
ObjectType::Tag,
Tag {
object: blob_oid,
object_type: ObjectType::Blob,
name: b"v1".to_vec(),
tagger: Some(b"Example <example@example.invalid> 0 +0000".to_vec()),
message: b"tag message\n".to_vec(),
raw_body: None,
}
.write(),
);
let tag_oid = db.write_object(tag).expect("test operation should succeed");
let reachable = collect_reachable_object_ids(&db, format, std::iter::once(tag_oid))
.expect("test operation should succeed");
assert!(reachable.contains(&tag_oid));
assert!(reachable.contains(&blob_oid));
let missing = ObjectId::from_hex(format, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
.expect("test operation should succeed");
let err = collect_reachable_object_ids(&db, format, std::iter::once(missing))
.expect_err("missing traversal root should error");
let kind = err.not_found_kind().expect("typed not found");
assert_eq!(kind.object_id(), Some(missing));
assert_eq!(
kind.missing_object_context(),
Some(MissingObjectContext::Traversal)
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_reachable_pack_empty_starts_create_no_pack() {
let root = temp_root("sley-reachable-empty");
let source_git_dir = root.join("source.git");
let destination_git_dir = root.join("destination.git");
fs::create_dir_all(source_git_dir.join("objects")).expect("test operation should succeed");
fs::create_dir_all(destination_git_dir.join("objects"))
.expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let source = FileObjectDatabase::from_git_dir(&source_git_dir, format);
let destination = FileObjectDatabase::from_git_dir(&destination_git_dir, format);
let result = install_reachable_pack(&source, &destination, format, Vec::<ObjectId>::new())
.expect("test operation should succeed");
assert!(result.is_none());
assert!(!destination_git_dir.join("objects").join("pack").exists());
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_reachable_pack_excluding_skips_fully_excluded_starts() {
let root = temp_root("sley-reachable-install-excluding");
let source_git_dir = root.join("source.git");
let destination_git_dir = root.join("destination.git");
fs::create_dir_all(source_git_dir.join("objects")).expect("test operation should succeed");
fs::create_dir_all(destination_git_dir.join("objects"))
.expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let source = FileObjectDatabase::from_git_dir(&source_git_dir, format);
let destination = FileObjectDatabase::from_git_dir(&destination_git_dir, format);
let object = EncodedObject::new(ObjectType::Blob, b"excluded install\n".to_vec());
let oid = source
.write_object(object)
.expect("test operation should succeed");
let excluded = HashSet::from([oid]);
let result = install_reachable_pack_excluding(
&source,
&destination,
format,
std::iter::once(oid),
&excluded,
)
.expect("test operation should succeed");
assert!(result.is_none());
assert!(!destination_git_dir.join("objects").join("pack").exists());
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_reachable_pack_supports_sha256() {
let root = temp_root("sley-reachable-pack-sha256");
let source_git_dir = root.join("source.git");
let destination_git_dir = root.join("destination.git");
fs::create_dir_all(source_git_dir.join("objects")).expect("test operation should succeed");
fs::create_dir_all(destination_git_dir.join("objects"))
.expect("test operation should succeed");
let format = ObjectFormat::Sha256;
let source = FileObjectDatabase::from_git_dir(&source_git_dir, format);
let destination = FileObjectDatabase::from_git_dir(&destination_git_dir, format);
let object = EncodedObject::new(ObjectType::Blob, b"sha256 reachable pack\n".to_vec());
let oid = source
.write_object(object.clone())
.expect("test operation should succeed");
let pack = build_reachable_pack(&source, format, std::iter::once(oid), &HashSet::new())
.expect("test operation should succeed")
.expect("sha256 reachable pack should be built");
assert!(pack.pack.starts_with(b"PACK"));
assert_eq!(pack.entries[0].oid, oid);
let result = install_reachable_pack(&source, &destination, format, std::iter::once(oid))
.expect("test operation should succeed")
.expect("sha256 reachable pack should be written");
assert_eq!(result.object_ids, vec![oid]);
assert!(
!destination
.loose()
.object_path(&oid)
.expect("test operation should succeed")
.exists()
);
assert_eq!(read_object_for_assert(&destination, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_helpers_accept_custom_raw_pack_installer() {
#[derive(Default)]
struct RecordingInstaller {
packs: std::cell::RefCell<Vec<Vec<u8>>>,
installed: std::cell::RefCell<Vec<ObjectId>>,
}
impl RawPackInstaller for RecordingInstaller {
fn install_raw_pack_from_reader_with_options<R>(
&self,
reader: &mut R,
_options: RawPackInstallOptions,
) -> Result<RawPackInstallResult>
where
R: Read,
{
let mut pack_bytes = Vec::new();
reader.read_to_end(&mut pack_bytes)?;
self.packs.borrow_mut().push(pack_bytes.to_vec());
let object_ids = self.installed.borrow().clone();
Ok(RawPackInstallResult { object_ids })
}
}
let format = ObjectFormat::Sha1;
let source = ObjectDatabase::new(format);
let object = EncodedObject::new(ObjectType::Blob, b"custom raw installer\n".to_vec());
let oid = source
.write_object(object)
.expect("test operation should succeed");
let installer = RecordingInstaller::default();
installer.installed.borrow_mut().push(oid);
let result = install_reachable_pack(&source, &installer, format, std::iter::once(oid))
.expect("test operation should succeed")
.expect("custom installer should receive pack");
assert_eq!(result.object_ids, installer.installed.into_inner());
let packs = installer.packs.into_inner();
assert_eq!(packs.len(), 1);
assert!(packs[0].starts_with(b"PACK"));
}
#[test]
fn file_database_reads_object_from_multi_pack_index() {
let root = temp_root("sley-file-odb-midx");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let first = EncodedObject::new(ObjectType::Blob, b"first packed\n".to_vec());
let second = EncodedObject::new(ObjectType::Blob, b"second packed\n".to_vec());
let first_oid = first
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let second_oid = second
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let first_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&first))
.expect("test operation should succeed");
let second_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&second))
.expect("test operation should succeed");
let first_pack_name = format!("pack-{}.idx", first_pack.checksum.to_hex());
let second_pack_name = format!("pack-{}.idx", second_pack.checksum.to_hex());
fs::write(
pack_dir.join(first_pack_name.replace(".idx", ".pack")),
first_pack.pack,
)
.expect("test operation should succeed");
fs::write(
pack_dir.join(second_pack_name.replace(".idx", ".pack")),
second_pack.pack,
)
.expect("test operation should succeed");
let midx = MultiPackIndex::write(
ObjectFormat::Sha1,
2,
&[first_pack_name, second_pack_name],
&[
sley_pack::MultiPackIndexEntry {
oid: first_oid,
pack_int_id: 0,
offset: first_pack.entries[0].offset,
force_large_offset: false,
},
sley_pack::MultiPackIndexEntry {
oid: second_oid,
pack_int_id: 1,
offset: second_pack.entries[0].offset,
force_large_offset: false,
},
],
)
.expect("test operation should succeed");
fs::write(pack_dir.join("multi-pack-index"), midx).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
assert!(
db.contains(&second_oid)
.expect("test operation should succeed")
);
assert_eq!(
db.resolve_prefix(&second_oid.to_hex()[..8])
.expect("test operation should succeed"),
ObjectPrefixResolution::Unique(second_oid)
);
assert_eq!(read_object_for_assert(&db, &second_oid), second);
assert_eq!(read_object_for_assert(&db, &first_oid), first);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_finds_pack_added_after_registry_was_cached() {
let root = temp_root("sley-file-odb-pack-added-late");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let first = EncodedObject::new(ObjectType::Blob, b"first late\n".to_vec());
let first_oid = first
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let first_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&first))
.expect("test operation should succeed");
db.install_pack(&first_pack)
.expect("test operation should succeed");
assert_eq!(read_object_for_assert(&db, &first_oid), first);
let second = EncodedObject::new(ObjectType::Blob, b"second late\n".to_vec());
let second_oid = second
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
assert!(matches!(
db.read_object(&second_oid),
Err(GitError::NotFound(_))
));
let second_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&second))
.expect("test operation should succeed");
db.install_pack(&second_pack)
.expect("test operation should succeed");
assert!(
db.contains(&second_oid)
.expect("test operation should succeed")
);
assert_eq!(read_object_for_assert(&db, &second_oid), second);
assert_eq!(read_object_for_assert(&db, &first_oid), first);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn object_presence_checker_finds_pack_added_after_registry_was_cached() {
let root = temp_root("sley-presence-checker-pack-added-late");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let first = EncodedObject::new(ObjectType::Blob, b"checker first late\n".to_vec());
let first_oid = first
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let first_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&first))
.expect("test operation should succeed");
db.install_pack(&first_pack)
.expect("test operation should succeed");
let second = EncodedObject::new(ObjectType::Blob, b"checker second late\n".to_vec());
let second_oid = second
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let mut checker = db.presence_checker();
assert!(
checker
.contains(&first_oid)
.expect("test operation should succeed")
);
assert!(
!checker
.contains(&second_oid)
.expect("test operation should succeed")
);
let second_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&second))
.expect("test operation should succeed");
db.install_pack(&second_pack)
.expect("test operation should succeed");
assert!(
checker
.contains(&second_oid)
.expect("test operation should succeed")
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_pack_registry_loads_indexes_lazily_and_refreshes_after_count_change() {
let root = temp_root("sley-file-odb-pack-registry-refresh");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let first = EncodedObject::new(ObjectType::Blob, b"registry first\n".to_vec());
let first_oid = first
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let first_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&first))
.expect("test operation should succeed");
db.install_pack(&first_pack)
.expect("test operation should succeed");
let first_registry = db
.cached_pack_registry(&pack_dir, false)
.expect("test operation should succeed");
assert_eq!(first_registry.fingerprint.idx_count, 1);
assert_eq!(first_registry.fingerprint.pack_count, 1);
assert_eq!(first_registry.packs.len(), 1);
assert!(
first_registry.packs[0]
.index.read().is_none()
);
assert!(
first_registry.packs[0]
.data
.lock()
.expect("test operation should succeed")
.is_none()
);
assert!(
db.contains(&first_oid)
.expect("test operation should succeed")
);
assert!(
first_registry.packs[0]
.index.read().is_some()
);
assert!(
first_registry.packs[0]
.data
.lock()
.expect("test operation should succeed")
.is_none()
);
assert_eq!(read_object_for_assert(&db, &first_oid), first);
assert!(
first_registry.packs[0]
.data
.lock()
.expect("test operation should succeed")
.is_some()
);
let second = EncodedObject::new(ObjectType::Blob, b"registry second\n".to_vec());
let second_oid = second
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let second_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&second))
.expect("test operation should succeed");
db.install_pack(&second_pack)
.expect("test operation should succeed");
let refreshed = db
.cached_pack_registry(&pack_dir, true)
.expect("test operation should succeed");
assert!(!Arc::ptr_eq(&first_registry, &refreshed));
assert_eq!(refreshed.fingerprint.idx_count, 2);
assert_eq!(refreshed.fingerprint.pack_count, 2);
assert_eq!(refreshed.packs.len(), 2);
assert_eq!(read_object_for_assert(&db, &second_oid), second);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_pack_search_hint_rebuilds_after_pack_added() {
let root = temp_root("sley-file-odb-pack-lookup-added-late");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let first = EncodedObject::new(ObjectType::Blob, b"first lookup\n".to_vec());
let second = EncodedObject::new(ObjectType::Blob, b"second lookup\n".to_vec());
let third = EncodedObject::new(ObjectType::Blob, b"third lookup\n".to_vec());
let first_oid = first
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let second_oid = second
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let third_oid = third
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let first_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&first))
.expect("test operation should succeed");
let second_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&second))
.expect("test operation should succeed");
db.install_pack(&first_pack)
.expect("test operation should succeed");
db.install_pack(&second_pack)
.expect("test operation should succeed");
assert_eq!(read_object_for_assert(&db, &first_oid), first);
assert_eq!(read_object_for_assert(&db, &second_oid), second);
assert!(matches!(
db.read_object(&third_oid),
Err(GitError::NotFound(_))
));
let third_pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&third))
.expect("test operation should succeed");
db.install_pack(&third_pack)
.expect("test operation should succeed");
assert_eq!(read_object_for_assert(&db, &third_oid), third);
assert_eq!(read_object_for_assert(&db, &first_oid), first);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn file_database_prefers_loose_object_over_packed_object() {
let root = temp_root("sley-file-odb-prefer-loose");
let git_dir = root.join(".git");
let pack_dir = git_dir.join("objects").join("pack");
fs::create_dir_all(&pack_dir).expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"same\n".to_vec());
let written = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
let pack_name = written.checksum.to_hex();
fs::write(
pack_dir.join(format!("pack-{pack_name}.pack")),
written.pack,
)
.expect("test operation should succeed");
fs::write(
pack_dir.join(format!("pack-{pack_name}.idx")),
written.index,
)
.expect("test operation should succeed");
let db = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let oid = db
.write_object(object.clone())
.expect("test operation should succeed");
assert_eq!(read_object_for_assert(&db, &oid), object);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn bundle_prerequisite_verification_reads_existing_objects() {
let db = ObjectDatabase::new(ObjectFormat::Sha1);
let oid = db
.write_object(EncodedObject::new(ObjectType::Blob, b"base\n".to_vec()))
.expect("test operation should succeed");
let bundle_bytes = format!("# v2 git bundle\n-{oid} base\n\n").into_bytes();
let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
.expect("test operation should succeed");
verify_bundle_prerequisites(&bundle, &db).expect("test operation should succeed");
}
#[test]
fn bundle_prerequisite_verification_reports_missing_objects() {
let db = ObjectDatabase::new(ObjectFormat::Sha1);
let missing = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"missing\n")
.expect("test operation should succeed");
let bundle_bytes = format!("# v2 git bundle\n-{missing} missing\n\n").into_bytes();
let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
.expect("test operation should succeed");
assert!(verify_bundle_prerequisites(&bundle, &db).is_err());
}
#[test]
fn unbundle_objects_writes_pack_entries_and_returns_refs() {
let prerequisite_reader = ObjectDatabase::new(ObjectFormat::Sha1);
let mut writer = ObjectDatabase::new(ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"bundle object\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
let bundle_bytes = format!("# v2 git bundle\n{oid} refs/heads/main\n\n")
.into_bytes()
.into_iter()
.chain(pack.pack)
.collect::<Vec<_>>();
let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
.expect("test operation should succeed");
let result = unbundle_objects(&bundle, &prerequisite_reader, &mut writer)
.expect("test operation should succeed");
assert_eq!(result.written_objects, vec![oid]);
assert_eq!(result.references, bundle.references);
assert_eq!(read_object_for_assert(&writer, &oid), object);
}
#[test]
fn install_bundle_pack_writes_pack_and_returns_refs() {
let root = temp_root("sley-install-bundle-pack");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let prerequisite_reader = ObjectDatabase::new(ObjectFormat::Sha1);
let database = FileObjectDatabase::from_git_dir(&git_dir, ObjectFormat::Sha1);
let object = EncodedObject::new(ObjectType::Blob, b"bundle pack object\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
let bundle_bytes = format!("# v2 git bundle\n{oid} refs/heads/main\n\n")
.into_bytes()
.into_iter()
.chain(pack.pack)
.collect::<Vec<_>>();
let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
.expect("test operation should succeed");
let result = install_bundle_pack(&bundle, &prerequisite_reader, &database)
.expect("test operation should succeed");
assert_eq!(result.written_objects, vec![oid]);
assert_eq!(result.references, bundle.references);
assert!(
database
.contains(&oid)
.expect("test operation should succeed")
);
assert_eq!(read_object_for_assert(&database, &oid), object);
assert!(
!database
.loose()
.object_path(&oid)
.expect("test operation should succeed")
.exists()
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn unpack_packfile_objects_writes_sha256_pack_entries() {
let writer = ObjectDatabase::new(ObjectFormat::Sha256);
let object = EncodedObject::new(ObjectType::Blob, b"transport pack object\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha256)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified(std::slice::from_ref(&object), ObjectFormat::Sha256)
.expect("test operation should succeed");
let result = unpack_packfile_objects(&pack.pack, ObjectFormat::Sha256, &writer)
.expect("test operation should succeed");
assert_eq!(result.written_objects, vec![oid]);
assert_eq!(read_object_for_assert(&writer, &oid), object);
}
#[test]
fn unbundle_objects_rejects_missing_prerequisites_before_writing() {
let prerequisite_reader = ObjectDatabase::new(ObjectFormat::Sha1);
let mut writer = ObjectDatabase::new(ObjectFormat::Sha1);
let missing = sley_core::object_id_for_bytes(ObjectFormat::Sha1, "blob", b"missing\n")
.expect("test operation should succeed");
let object = EncodedObject::new(ObjectType::Blob, b"bundle object\n".to_vec());
let oid = object
.object_id(ObjectFormat::Sha1)
.expect("test operation should succeed");
let pack = PackFile::write_undeltified_sha1(std::slice::from_ref(&object))
.expect("test operation should succeed");
let bundle_bytes =
format!("# v2 git bundle\n-{missing} missing\n{oid} refs/heads/main\n\n")
.into_bytes()
.into_iter()
.chain(pack.pack)
.collect::<Vec<_>>();
let bundle = Bundle::parse(&bundle_bytes, ObjectFormat::Sha1)
.expect("test operation should succeed");
assert!(unbundle_objects(&bundle, &prerequisite_reader, &mut writer).is_err());
assert!(!writer.contains(&oid));
}
fn write_commit_graph(
db: &mut FileObjectDatabase,
payload: &[u8],
) -> Vec<(ObjectId, EncodedObject)> {
let blob = EncodedObject::new(ObjectType::Blob, payload.to_vec());
let blob_oid = db
.write_object(blob.clone())
.expect("test operation should succeed");
let tree = EncodedObject::new(
ObjectType::Tree,
Tree {
entries: vec![TreeEntry {
mode: 0o100644,
name: BString::from(b"payload.txt"),
oid: blob_oid,
}],
}
.write(),
);
let tree_oid = db
.write_object(tree.clone())
.expect("test operation should succeed");
let identity = b"Example <example@example.invalid> 0 +0000".to_vec();
let commit = EncodedObject::new(
ObjectType::Commit,
Commit {
tree: tree_oid,
parents: Vec::new(),
author: identity.clone(),
committer: identity,
encoding: None,
message: b"initial\n".to_vec(),
}
.write(),
);
let commit_oid = db
.write_object(commit.clone())
.expect("test operation should succeed");
vec![(commit_oid, commit), (tree_oid, tree), (blob_oid, blob)]
}
fn repack_all_objects_consolidates_loose_and_pack(format: ObjectFormat) {
let root = temp_root("sley-repack-all");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let mut db = FileObjectDatabase::from_git_dir(&git_dir, format);
let packed_blob = EncodedObject::new(ObjectType::Blob, b"already packed\n".to_vec());
let packed_oid = packed_blob
.object_id(format)
.expect("test operation should succeed");
let existing_pack = PackFile::write_undeltified(std::slice::from_ref(&packed_blob), format)
.expect("test operation should succeed");
let existing = db
.install_pack(&existing_pack)
.expect("test operation should succeed");
let graph = write_commit_graph(&mut db, b"repack payload\n");
let mut expected: HashMap<ObjectId, EncodedObject> = graph.iter().cloned().collect();
expected.insert(packed_oid, packed_blob.clone());
let result = repack_all_objects(&git_dir, format)
.expect("test operation should succeed")
.expect("repository has objects");
assert_eq!(result.object_count, expected.len());
let parsed = PackFile::parse(&result.pack, format).expect("test operation should succeed");
assert_eq!(parsed.entries.len(), expected.len());
for entry in &parsed.entries {
let want = expected
.get(&entry.entry.oid)
.expect("packed object was in the repository");
assert_eq!(&entry.object, want);
assert_eq!(
entry
.object
.object_id(format)
.expect("test operation should succeed"),
entry.entry.oid
);
}
let idx = PackIndex::parse(&result.idx, format).expect("test operation should succeed");
assert_eq!(idx.pack_checksum, parsed.checksum);
assert_eq!(idx.entries.len(), expected.len());
assert_eq!(result.obsolete_packs, vec![existing.pack_path.clone()]);
let mut want_loose: Vec<ObjectId> = graph.iter().map(|(oid, _)| *oid).collect();
want_loose.sort_by_key(ObjectId::to_hex);
assert_eq!(result.packed_loose, want_loose);
assert!(!result.packed_loose.contains(&packed_oid));
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn repack_all_objects_consolidates_loose_and_pack_sha1() {
repack_all_objects_consolidates_loose_and_pack(ObjectFormat::Sha1);
}
#[test]
fn repack_all_objects_consolidates_loose_and_pack_sha256() {
repack_all_objects_consolidates_loose_and_pack(ObjectFormat::Sha256);
}
#[test]
fn repack_all_objects_returns_none_for_empty_repository() {
let root = temp_root("sley-repack-empty");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
assert!(
repack_all_objects(&git_dir, ObjectFormat::Sha1)
.expect("test operation should succeed")
.is_none()
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_repack_result_writes_pack_without_pruning_by_default() {
let root = temp_root("sley-repack-install-nodelete");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let mut db = FileObjectDatabase::from_git_dir(&git_dir, format);
let graph = write_commit_graph(&mut db, b"install no prune\n");
let result = repack_all_objects(&git_dir, format)
.expect("test operation should succeed")
.expect("test operation should succeed");
install_repack_result(&git_dir, format, &result, false)
.expect("test operation should succeed");
let parsed = PackFile::parse(&result.pack, format).expect("test operation should succeed");
let pack_dir = git_dir.join("objects").join("pack");
let pack_path = pack_dir.join(format!("pack-{}.pack", parsed.checksum.to_hex()));
let idx_path = pack_dir.join(format!("pack-{}.idx", parsed.checksum.to_hex()));
assert!(pack_path.exists());
assert!(idx_path.exists());
for (oid, object) in &graph {
assert!(
db.loose()
.object_path(oid)
.expect("test operation should succeed")
.exists()
);
assert_eq!(read_object_for_assert(&db, oid), *object);
}
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_repack_result_prunes_obsolete_packs_and_loose_objects() {
let root = temp_root("sley-repack-install-prune");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let mut db = FileObjectDatabase::from_git_dir(&git_dir, format);
let packed_blob = EncodedObject::new(ObjectType::Blob, b"prune packed\n".to_vec());
let existing_pack = PackFile::write_undeltified(std::slice::from_ref(&packed_blob), format)
.expect("test operation should succeed");
let existing = db
.install_pack(&existing_pack)
.expect("test operation should succeed");
let graph = write_commit_graph(&mut db, b"prune payload\n");
let result = repack_all_objects(&git_dir, format)
.expect("test operation should succeed")
.expect("test operation should succeed");
let new_pack_checksum = PackFile::parse(&result.pack, format)
.expect("test operation should succeed")
.checksum;
install_repack_result(&git_dir, format, &result, true)
.expect("test operation should succeed");
assert!(!existing.pack_path.exists());
assert!(!existing.index_path.exists());
for (oid, _) in &graph {
assert!(
!db.loose()
.object_path(oid)
.expect("test operation should succeed")
.exists()
);
}
let pack_dir = git_dir.join("objects").join("pack");
assert!(
pack_dir
.join(format!("pack-{}.pack", new_pack_checksum.to_hex()))
.exists()
);
let reopened = FileObjectDatabase::from_git_dir(&git_dir, format);
for (oid, object) in &graph {
assert!(
reopened
.contains(oid)
.expect("test operation should succeed")
);
assert_eq!(read_object_for_assert(&reopened, oid), *object);
}
let packed_oid = packed_blob
.object_id(format)
.expect("test operation should succeed");
assert_eq!(read_object_for_assert(&reopened, &packed_oid), packed_blob);
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_repack_result_preserves_keep_and_promisor_packs() {
let root = temp_root("sley-repack-install-keep-promisor");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let mut db = FileObjectDatabase::from_git_dir(&git_dir, format);
let keep_blob = EncodedObject::new(ObjectType::Blob, b"keep protected\n".to_vec());
let keep_pack = PackFile::write_undeltified(std::slice::from_ref(&keep_blob), format)
.expect("test operation should succeed");
let keep_install = db
.install_pack(&keep_pack)
.expect("test operation should succeed");
let keep_sidecar = keep_install.pack_path.with_extension("keep");
fs::write(&keep_sidecar, b"").expect("test operation should succeed");
let promisor_blob = EncodedObject::new(ObjectType::Blob, b"promisor protected\n".to_vec());
let promisor_pack =
PackFile::write_undeltified(std::slice::from_ref(&promisor_blob), format)
.expect("test operation should succeed");
let promisor_install = db
.install_pack_with_options(
&promisor_pack,
RawPackInstallOptions {
promisor: true,
..Default::default()
},
)
.expect("test operation should succeed");
let promisor_sidecar = promisor_install
.promisor_path
.clone()
.expect("promisor sidecar");
let graph = write_commit_graph(&mut db, b"new consolidated payload\n");
let result = repack_all_objects(&git_dir, format)
.expect("test operation should succeed")
.expect("test operation should succeed");
assert!(result.obsolete_packs.contains(&keep_install.pack_path));
assert!(result.obsolete_packs.contains(&promisor_install.pack_path));
install_repack_result(&git_dir, format, &result, true)
.expect("test operation should succeed");
for path in [
&keep_install.pack_path,
&keep_install.index_path,
&keep_sidecar,
&promisor_install.pack_path,
&promisor_install.index_path,
&promisor_sidecar,
] {
assert!(path.exists(), "{} should be preserved", path.display());
}
for (oid, _) in &graph {
assert!(
!db.loose()
.object_path(oid)
.expect("test operation should succeed")
.exists()
);
}
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn install_repack_result_keeps_loose_object_absent_from_new_pack() {
let root = temp_root("sley-repack-install-safety");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let mut db = FileObjectDatabase::from_git_dir(&git_dir, format);
let graph = write_commit_graph(&mut db, b"safety packed\n");
let mut result = repack_all_objects(&git_dir, format)
.expect("test operation should succeed")
.expect("test operation should succeed");
let stray = EncodedObject::new(ObjectType::Blob, b"never packed\n".to_vec());
let stray_oid = db
.write_object(stray.clone())
.expect("test operation should succeed");
assert!(!result.packed_loose.contains(&stray_oid));
result.packed_loose.push(stray_oid);
install_repack_result(&git_dir, format, &result, true)
.expect("test operation should succeed");
assert!(
db.loose()
.object_path(&stray_oid)
.expect("test operation should succeed")
.exists()
);
assert_eq!(read_object_for_assert(&db, &stray_oid), stray);
for (oid, _) in &graph {
assert!(
!db.loose()
.object_path(oid)
.expect("test operation should succeed")
.exists()
);
}
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn prune_unreachable_loose_reports_and_deletes_only_unreachable() {
let root = temp_root("sley-prune-unreachable");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let mut db = FileObjectDatabase::from_git_dir(&git_dir, format);
let graph = write_commit_graph(&mut db, b"reachable payload\n");
let commit_oid = graph[0].0.clone();
let dangling = EncodedObject::new(ObjectType::Blob, b"dangling\n".to_vec());
let dangling_oid = db
.write_object(dangling)
.expect("test operation should succeed");
let reported = prune_unreachable_loose(&git_dir, format, [commit_oid], false)
.expect("test operation should succeed");
assert_eq!(reported, vec![dangling_oid]);
assert!(
db.loose()
.object_path(&dangling_oid)
.expect("test operation should succeed")
.exists()
);
let deleted = prune_unreachable_loose(&git_dir, format, [commit_oid], true)
.expect("test operation should succeed");
assert_eq!(deleted, vec![dangling_oid]);
assert!(
!db.loose()
.object_path(&dangling_oid)
.expect("test operation should succeed")
.exists()
);
for (oid, object) in &graph {
assert!(
db.loose()
.object_path(oid)
.expect("test operation should succeed")
.exists()
);
assert_eq!(read_object_for_assert(&db, oid), *object);
}
fs::remove_dir_all(root).expect("test operation should succeed");
}
#[test]
fn prune_unreachable_loose_ignores_gitlink_targets() {
let root = temp_root("sley-prune-gitlink");
let git_dir = root.join(".git");
fs::create_dir_all(git_dir.join("objects")).expect("test operation should succeed");
let format = ObjectFormat::Sha1;
let db = FileObjectDatabase::from_git_dir(&git_dir, format);
let submodule_oid = ObjectId::from_hex(format, "1111111111111111111111111111111111111111")
.expect("test operation should succeed");
let tree = EncodedObject::new(
ObjectType::Tree,
Tree {
entries: vec![TreeEntry {
mode: 0o160000,
name: BString::from(b"submodule"),
oid: submodule_oid,
}],
}
.write(),
);
let tree_oid = db
.write_object(tree)
.expect("test operation should succeed");
let identity = b"Example <example@example.invalid> 0 +0000".to_vec();
let commit = EncodedObject::new(
ObjectType::Commit,
Commit {
tree: tree_oid,
parents: Vec::new(),
author: identity.clone(),
committer: identity,
encoding: None,
message: b"gitlink\n".to_vec(),
}
.write(),
);
let commit_oid = db
.write_object(commit)
.expect("test operation should succeed");
let dangling = EncodedObject::new(ObjectType::Blob, b"dangling with gitlink\n".to_vec());
let dangling_oid = db
.write_object(dangling)
.expect("test operation should succeed");
let deleted = prune_unreachable_loose(&git_dir, format, [commit_oid], true)
.expect("test operation should succeed");
assert_eq!(deleted, vec![dangling_oid]);
assert!(
!db.loose()
.object_path(&dangling_oid)
.expect("test operation should succeed")
.exists()
);
fs::remove_dir_all(root).expect("test operation should succeed");
}
fn temp_root(prefix: &str) -> PathBuf {
std::env::temp_dir().join(format!(
"{prefix}-{}-{}",
std::process::id(),
TEMPFILE_COUNTER.fetch_add(1, Ordering::Relaxed)
))
}
}