#![allow(clippy::unwrap_used)]
mod common;
use std::fs;
use std::path::Path;
use common::Repo;
use mkit_cli::remote_dispatch::{pull_all, push_all, push_branch_with_limits};
use mkit_core::hash::Hash;
use mkit_core::layout::RepoLayout;
use mkit_core::protocol::{PackKey, RefWriteCondition, Transport};
use mkit_core::refs;
use mkit_core::store::ObjectStore;
use mkit_core::transfer::{self, PackListNode};
use mkit_transport_memory::MemoryTransport;
const TINY_PAYLOAD_CAP: u64 = 4096;
fn head_hash(dir: &Path) -> Hash {
refs::read_ref(&RepoLayout::single(dir), "main")
.unwrap()
.unwrap()
}
fn packmap_chain(tx: &dyn Transport, branch: &str) -> Vec<PackListNode> {
let mut nodes = Vec::new();
let mut cursor = tx.read_ref(&format!("refs/mkit/packmap/{branch}")).unwrap();
while let Some(key) = cursor {
let bytes = tx.download_blob(&PackKey::from_hash(key)).unwrap();
let node = transfer::decode_packlist(&bytes).unwrap();
cursor = node.prev;
nodes.push(node);
}
nodes
}
fn filler(seed: u64, len: usize) -> Vec<u8> {
let mut buf = vec![0u8; len];
let mut state = seed | 1;
for chunk in buf.chunks_mut(8) {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
chunk.copy_from_slice(&state.to_le_bytes()[..chunk.len()]);
}
buf
}
#[test]
fn oversized_push_splits_into_one_node_with_multiple_packs_and_stays_fetchable() {
let alice = Repo::new();
let files: Vec<(String, Vec<u8>)> = (0..8)
.map(|i| (format!("f{i}.bin"), filler(0xA11C_E000 + i, 2048)))
.collect();
for (name, body) in &files {
alice.write(name, body);
}
alice.ok(&["add", "."]);
alice.ok(&["commit", "-m", "eight incompressible files"]);
let tip = head_hash(alice.path());
let tx = MemoryTransport::new();
let store = ObjectStore::open(&RepoLayout::single(alice.path())).unwrap();
push_branch_with_limits(
&tx,
&store,
"main",
tip,
RefWriteCondition::Missing,
0, TINY_PAYLOAD_CAP,
)
.expect("oversized push must split, not fail with PackfileTooLarge");
let chain = packmap_chain(&tx, "main");
assert_eq!(chain.len(), 1, "one push records exactly one packmap node");
assert!(
chain[0].packs.len() > 1,
"8 * 2 KiB of incompressible content must exceed a {TINY_PAYLOAD_CAP}-byte cap and split, \
got {} pack(s)",
chain[0].packs.len()
);
for pack_key in &chain[0].packs {
tx.download_pack(&PackKey::from_hash(*pack_key))
.unwrap_or_else(|e| panic!("pack {pack_key:?} must be downloadable: {e}"));
}
assert_eq!(tx.read_ref("refs/heads/main").unwrap(), Some(tip));
let carol = Repo::new();
pull_all(carol.path(), &tx, "default", None).expect("clone from a multi-pack node");
assert_eq!(head_hash(carol.path()), tip);
for (name, body) in &files {
assert_eq!(
fs::read(carol.path().join(name)).unwrap(),
*body,
"{name} must round-trip byte-identical through a multi-pack push"
);
}
}
#[test]
fn normal_push_still_produces_exactly_one_pack() {
let alice = Repo::new();
alice.commit_file("small.txt", b"just one small file", "small commit");
let tip = head_hash(alice.path());
let tx = MemoryTransport::new();
push_all(alice.path(), &tx).expect("normal push");
assert_eq!(tx.read_ref("refs/heads/main").unwrap(), Some(tip));
let chain = packmap_chain(&tx, "main");
assert_eq!(chain.len(), 1);
assert_eq!(
chain[0].packs.len(),
1,
"a plan well under the payload cap must still produce exactly one pack"
);
}