makepad/libs/git/tests/local_clone_test.rs
2026-02-18 10:04:26 +01:00

253 lines
7.9 KiB
Rust

use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
}
fn current_branch(repo_root: &Path) -> String {
let head = fs::read_to_string(repo_root.join(".git/HEAD")).unwrap();
head.trim()
.strip_prefix("ref: refs/heads/")
.unwrap_or("main")
.to_string()
}
fn git(dir: &Path, args: &[&str]) -> (bool, String, String) {
let out = Command::new("git")
.args(args)
.current_dir(dir)
.output()
.unwrap();
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).trim().to_string(),
String::from_utf8_lossy(&out.stderr).trim().to_string(),
)
}
#[test]
fn test_local_clone_commit_merge() {
let root = repo_root();
let branch = current_branch(&root);
let checkout1 = root.join("local/checkout1");
let checkout2 = root.join("local/checkout2");
// Clean previous runs
let _ = fs::remove_dir_all(&checkout1);
let _ = fs::remove_dir_all(&checkout2);
println!(
"\n=== Local depth=1 clone: {} branch={} ===",
root.display(),
branch
);
// --- Clone into checkout1 and checkout2 ---
let t1 = makepad_git::local_clone_depth1(&root, &checkout1, Some(&branch))
.expect("clone checkout1 failed");
println!(
"\ncheckout1: {:.1}ms ({} files, {:.1}MB)",
t1.total_ms,
t1.num_files,
t1.bytes_written as f64 / 1_048_576.0
);
println!(
" resolve={:.1}ms setup={:.1}ms checkout={:.1}ms",
t1.resolve_ms, t1.setup_ms, t1.checkout_ms
);
println!(
" tree_walk={:.1}ms parallel={:.1}ms",
t1.tree_walk_ms, t1.parallel_ms
);
let t2 = makepad_git::local_clone_depth1(&root, &checkout2, Some(&branch))
.expect("clone checkout2 failed");
println!(
"\ncheckout2: {:.1}ms ({} files, {:.1}MB)",
t2.total_ms,
t2.num_files,
t2.bytes_written as f64 / 1_048_576.0
);
// --- Git CLI comparison ---
let dir_c = root.join("local/checkout_gitcli");
let _ = fs::remove_dir_all(&dir_c);
let t_c = Instant::now();
let _ = Command::new("git")
.args([
"clone",
"--depth=1",
"--branch",
&branch,
"--local",
root.to_str().unwrap(),
dir_c.to_str().unwrap(),
])
.output()
.unwrap();
let ms_c = t_c.elapsed().as_secs_f64() * 1000.0;
let _ = fs::remove_dir_all(&dir_c);
println!("\ngit clone --depth=1: {:.1}ms", ms_c);
println!("Speedup: {:.1}x", ms_c / t1.total_ms);
// --- Verify checkout1 with git CLI ---
println!("\n--- Verifying checkout1 ---");
let (ok, stdout, stderr) = git(&checkout1, &["status", "--short"]);
println!(" git status: ok={} entries={}", ok, stdout.lines().count());
if !stderr.is_empty() {
println!(" stderr: {}", stderr);
}
if !stdout.is_empty() {
// Print first few entries to see what's wrong
for line in stdout.lines().take(5) {
println!(" {}", line);
}
}
let (_, log, _) = git(&checkout1, &["log", "--oneline", "-1"]);
println!(" git log: {}", log);
let (_, diff, _) = git(&checkout1, &["diff", "--stat", "HEAD"]);
if diff.is_empty() {
println!(" working tree: clean");
} else {
println!(" working tree: {} files differ", diff.lines().count());
}
// --- Make a commit in checkout2 ---
println!("\n--- Committing in checkout2 ---");
fs::write(
checkout2.join("test_from_checkout2.txt"),
"hello from checkout2\n",
)
.unwrap();
let mut repo2 = makepad_git::Repository::open(&checkout2).unwrap();
repo2.stage_file("test_from_checkout2.txt").unwrap();
let sig = makepad_git::Signature {
name: "Test".into(),
email: "info@makepad.nl".into(),
timestamp: 1700000000,
tz_offset: "+0000".into(),
};
let commit2_oid = repo2
.commit("commit from checkout2\n", sig.clone())
.unwrap();
println!(" new commit: {}", commit2_oid);
// Verify with git CLI
let (_, log2, _) = git(&checkout2, &["log", "--oneline", "-3"]);
println!(
" git log:\n{}",
log2.lines()
.map(|l| format!(" {}", l))
.collect::<Vec<_>>()
.join("\n")
);
// --- Merge checkout2 into checkout1 ---
println!("\n--- Merging checkout2 -> checkout1 ---");
let merge_start = Instant::now();
// Copy new objects from checkout2 to checkout1 (just the loose ones from the commit)
let c2_objects = checkout2.join(".git/objects");
let c1_objects = checkout1.join(".git/objects");
copy_loose_objects(&c2_objects, &c1_objects);
// Create a branch in checkout1 for the merge source
let c1_git = checkout1.join(".git");
makepad_git::refs::write_ref(&c1_git, "refs/heads/_from_checkout2", &commit2_oid).unwrap();
let mut repo1 = makepad_git::Repository::open(&checkout1).unwrap();
// Debug: can we read the HEAD commit through alternates?
let head1 = repo1.head_oid().unwrap();
println!(" checkout1 HEAD: {}", head1);
match repo1.read_commit(&head1) {
Ok(c) => println!(" read_commit OK: tree={}", c.tree),
Err(e) => println!(" read_commit FAILED: {}", e),
}
let result = repo1.merge_branch("_from_checkout2", sig).unwrap();
let merge_ms = merge_start.elapsed().as_secs_f64() * 1000.0;
println!(" result: {:?}", result);
println!(" time: {:.1}ms", merge_ms);
// --- Verify merge result ---
println!("\n--- Verifying merge in checkout1 ---");
assert!(
checkout1.join("test_from_checkout2.txt").exists(),
"merged file must exist"
);
assert_eq!(
fs::read_to_string(checkout1.join("test_from_checkout2.txt")).unwrap(),
"hello from checkout2\n"
);
println!(" file content: OK");
let (_, log1, _) = git(&checkout1, &["log", "--oneline", "-5"]);
println!(
" git log:\n{}",
log1.lines()
.map(|l| format!(" {}", l))
.collect::<Vec<_>>()
.join("\n")
);
let (_, head1, _) = git(&checkout1, &["rev-parse", "HEAD"]);
println!(" HEAD: {}", head1);
let (_ok, diff1, _) = git(&checkout1, &["diff", "--stat", "HEAD"]);
if diff1.is_empty() {
println!(" working tree: clean");
} else {
println!(" working tree: {} files differ", diff1.lines().count());
}
let (_, status1, _) = git(&checkout1, &["status", "--short"]);
if status1.is_empty() {
println!(" git status: clean");
} else {
println!(" git status: {} entries", status1.lines().count());
for line in status1.lines().take(10) {
println!(" {}", line);
}
}
// --- Summary ---
println!("\n=== SUMMARY ===");
println!(
"Clone: {:.1}ms (vs git {:.1}ms, {:.1}x faster)",
t1.total_ms,
ms_c,
ms_c / t1.total_ms
);
println!("Merge: {:.1}ms", merge_ms);
}
fn copy_loose_objects(src: &Path, dst: &Path) {
if let Ok(entries) = fs::read_dir(src) {
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.len() == 2 && entry.path().is_dir() {
let dst_sub = dst.join(&name);
let _ = fs::create_dir_all(&dst_sub);
if let Ok(sub_entries) = fs::read_dir(entry.path()) {
for sub in sub_entries.flatten() {
let dst_file = dst_sub.join(sub.file_name());
if !dst_file.exists() {
let _ = fs::copy(sub.path(), dst_file);
}
}
}
}
}
}
}