use super::*;
#[test]
fn a_branchs_depth_one_reply_is_linked_while_its_tip_stays() -> anyhow::Result<()> {
use std::os::unix::fs::MetadataExt as _;
let root = tempfile::tempdir()?;
let store = RepoStore::open(&root.path().join("repo-store"));
let source = init_test_source(root.path());
git(&source, &["branch", "twin"]);
let (alone, first) = export_against(root.path(), &source, "export-1", None, &store);
assert!(super::fetch_replies(&alone)?.shallow_branches.is_empty());
commit(&source, "two.txt", "two\n", "two");
let (apart, second) = export_against(root.path(), &source, "export-2", Some(&first), &store);
let twin = git(&source, &["rev-parse", "twin"]);
assert_eq!(
super::fetch_replies(&apart)?.shallow_branches,
[twin.clone()]
);
commit(&source, "three.txt", "three\n", "three");
let (third, prior) = export_against(root.path(), &source, "export-3", Some(&second), &store);
let inode = |dir: &Path, name: &str| {
std::fs::metadata(dir.join("smart").join(name))
.unwrap()
.ino()
};
assert_eq!(inode(&apart, "shallow-0"), inode(&third, "shallow-0"));
assert_ne!(inode(&apart, "shallow-head"), inode(&third, "shallow-head"));
for n in 0..super::DEPTH_ONE_BRANCHES {
commit(&source, "n.txt", &format!("{n}\n"), "n");
git(&source, &["branch", &format!("b{n}")]);
}
let (many, _) = export_against(root.path(), &source, "export-4", Some(&prior), &store);
assert!(super::fetch_replies(&many)?.shallow_branches.is_empty());
assert!(!many.join("smart/shallow-0").exists());
Ok(())
}
#[test]
fn shallow_head_is_built_from_the_prior_reply() -> anyhow::Result<()> {
let root = tempfile::tempdir()?;
let store = RepoStore::open(&root.path().join("repo-store"));
let source = init_test_source(root.path());
for n in 0..40 {
commit(&source, &format!("f-{n:02}.txt"), &noise(n, 64 * 1024), "f");
}
commit(&source, "gone.txt", &noise(50, 4096), "gone");
let gone = git(&source, &["rev-parse", "main:gone.txt"]);
let (_, prior) = export_against(root.path(), &source, "export-1", None, &store);
git(&source, &["rm", "-q", "gone.txt"]);
commit(&source, "new.txt", &noise(51, 4096), "new");
let (second, _) = export_against(root.path(), &source, "export-2", Some(&prior), &store);
let tip = git(&source, &["rev-parse", "main"]);
let reply = std::fs::read(second.join("smart/shallow-head"))?;
let objects = assert_depth_one_pack(root.path(), &source, &reply, &tip);
assert!(!objects.contains(&gone));
assert!(
pack_entry_types(&reply)
.iter()
.all(|kind| (1..=4).contains(kind))
);
let after = second.join("smart/shallow-head");
let map = |path: &Path| crate::depth_one::tests::map_of(path, knot_types::ObjectFormat::SHA1);
let (entries, padding) = map(&after).expect("a map");
assert_eq!(entries.len(), objects.len());
assert_ne!(entries[0], tip, "built in the prior's order");
assert!(padding > 0, "padding to share blocks");
#[cfg(target_os = "linux")]
if reflinks_here(root.path()) {
let blocks = std::fs::metadata(&after)?.len() / 4096;
let shared = (0..blocks)
.filter(|block| shares_extent(&after, block * 4096))
.count() as u64;
assert!(
shared * 10 >= blocks * 9,
"{shared} of {blocks} blocks shared"
);
}
Ok(())
}
#[test]
fn a_moved_branch_is_built_from_its_own_prior_reply() -> anyhow::Result<()> {
let root = tempfile::tempdir()?;
let store = RepoStore::open(&root.path().join("repo-store"));
let source = init_test_source(root.path());
for (b, branch) in ["a", "b", "c"].into_iter().enumerate() {
git(&source, &["checkout", "-q", "--orphan", branch]);
git(&source, &["rm", "-q", "-r", "--cached", "."]);
for n in 0..40 {
let seed = 1000 * (b as u64 + 1) + n;
commit(
&source,
&format!("{branch}-{n:02}.txt"),
&noise(seed, 32 * 1024),
branch,
);
}
}
git(&source, &["checkout", "-q", "-f", "main"]);
let (_, prior) = export_against(root.path(), &source, "export-1", None, &store);
let tips = || -> Vec<String> {
let mut tips: Vec<String> = ["a", "b", "c"]
.iter()
.map(|branch| git(&source, &["rev-parse", branch]))
.collect();
tips.sort();
tips
};
let slot = |tip: &str| tips().iter().position(|t| t == tip).unwrap();
let before = slot(&git(&source, &["rev-parse", "a"]));
git(&source, &["checkout", "-q", "a"]);
for n in 0.. {
commit(&source, "a-00.txt", &format!("{n}\n"), "moved");
if slot(&git(&source, &["rev-parse", "a"])) != before {
break;
}
}
git(&source, &["checkout", "-q", "main"]);
let (second, _) = export_against(root.path(), &source, "export-2", Some(&prior), &store);
let tip = git(&source, &["rev-parse", "a"]);
let reply = second.join("smart").join(format!("shallow-{}", slot(&tip)));
assert_depth_one_pack(root.path(), &source, &std::fs::read(&reply)?, &tip);
let (entries, _) =
crate::depth_one::tests::map_of(&reply, knot_types::ObjectFormat::SHA1).expect("a map");
assert_ne!(entries[0], tip, "built from a's prior reply");
Ok(())
}
#[test]
fn shallow_head_is_cut_afresh_without_a_good_prior() -> anyhow::Result<()> {
let root = tempfile::tempdir()?;
let store = RepoStore::open(&root.path().join("repo-store"));
let source = init_test_source(root.path());
for n in 0..40 {
commit(&source, &format!("a-{n:02}.txt"), &noise(n, 64 * 1024), "a");
}
let (_, prior) = export_against(root.path(), &source, "export-1", None, &store);
let cut = |name: &str, prior: &ExportPrior| {
commit(&source, name, "x\n", name);
let (export, next) = export_against(root.path(), &source, name, Some(prior), &store);
let reply = export.join("smart/shallow-head");
let tip = git(&source, &["rev-parse", "main"]);
assert_depth_one_pack(root.path(), &source, &std::fs::read(&reply).unwrap(), &tip);
let (entries, _) =
crate::depth_one::tests::map_of(&reply, knot_types::ObjectFormat::SHA1).expect("a map");
(next, entries[0] == tip)
};
let (prior, fresh) = cut("b.txt", &prior);
assert!(!fresh, "built from a good prior");
let map = store
.entry_path(super::EXPORT_CACHE_KIND, &prior.fingerprint)
.join("smart/shallow-head.map");
let mut bytes = std::fs::read(&map)?;
bytes[41] ^= 1;
std::fs::remove_file(&map)?;
std::fs::write(&map, bytes)?;
let (prior, fresh) = cut("c.txt", &prior);
assert!(fresh, "a wrong map");
for n in 0..30 {
commit(
&source,
&format!("a-{n:02}.txt"),
&noise(n + 50, 64 * 1024),
"a",
);
}
let (_, fresh) = cut("d.txt", &prior);
assert!(fresh, "too few bytes shared");
Ok(())
}
#[test]
fn a_depth_one_reply_of_many_changed_small_files_is_built() {
let mut tree: Vec<(String, usize)> = (0..40)
.map(|n| (format!("big-{n:02}.txt"), 64 * 1024))
.collect();
tree.extend((0..100).map(|n| (format!("small/{n:03}.txt"), 200)));
let (fresh, stayed) = cut_after_change(&tree, &(40..140).collect::<Vec<_>>());
assert!(stayed < 0.5, "{stayed} of the objects stayed");
assert!(!fresh, "built from the prior's");
}
#[test]
fn a_depth_one_reply_kept_in_runs_too_short_to_share_is_cut() {
let tree: Vec<(String, usize)> = (0..400).map(|n| (format!("f-{n:03}.txt"), 1024)).collect();
let changed: Vec<usize> = (0..400).step_by(12).collect();
let (fresh, stayed) = cut_after_change(&tree, &changed);
assert!(stayed > 0.9, "{stayed} of the objects stayed");
assert!(fresh, "cut afresh");
}
#[test]
fn a_reply_padded_past_its_share_is_framed_again() -> anyhow::Result<()> {
let root = tempfile::tempdir()?;
let store = RepoStore::open(&root.path().join("repo-store"));
let source = init_test_source(root.path());
// fixed dates, because a new time moves every offset and the padding after it
let date = |n: u64| format!("{} +0000", 1_600_000_000 + n);
for n in 0..60 {
let name = format!("f-{n:02}.txt");
commit_dated(&source, &name, &noise(n, 128 * 1024), "f", Some(&date(n)));
}
let (_, mut prior) = export_against(root.path(), &source, "export-0", None, &store);
let mut paddings = Vec::new();
for step in 1..40 {
// two far apart changes per step split the kept runs, and the padding grows
for at in [step * 7 % 60, (step * 7 + 30) % 60] {
let name = format!("f-{at:02}.txt");
let text = noise(100 + step * 60 + at, 128 * 1024);
commit_dated(
&source,
&name,
&text,
"changed",
Some(&date(100 + step * 60 + at)),
);
}
let (export, next) = export_against(
root.path(),
&source,
&format!("export-{step}"),
Some(&prior),
&store,
);
let reply = export.join("smart/shallow-head");
let (_, padding) =
crate::depth_one::tests::map_of(&reply, knot_types::ObjectFormat::SHA1).expect("a map");
paddings.push(padding);
prior = next;
if paddings.len() > 1 && padding == 0 {
let tip = git(&source, &["rev-parse", "main"]);
assert_depth_one_pack(root.path(), &source, &std::fs::read(&reply)?, &tip);
return Ok(());
}
}
panic!("never framed again: {paddings:?}");
}
#[test]
fn shallow_head_follows_the_exported_head() -> anyhow::Result<()> {
let root = tempfile::tempdir()?;
let source = init_test_source(root.path());
let mut snap = snapshot(&source, heads(&source));
snap.head_capture = crate::snapshot::HeadCapture {
resolved: None,
symbolic: None,
unborn: true,
};
let export = root.path().join("export");
export_repo_test(root.path(), &snap, &export);
let main = git(&source, &["rev-parse", "main"]);
assert_eq!(super::fetch_replies(&export)?.shallow_head, Some(main));
Ok(())
}