use std::io::Cursor;
use guts_git::{advertise_refs, receive_pack, PackBuilder, PktLine, PktLineWriter};
use guts_storage::{GitObject, ObjectId, ObjectStore, ObjectType, Repository};
fn create_tree(store: &ObjectStore, entries: &[(&str, ObjectId)]) -> ObjectId {
let mut data = Vec::new();
for (name, id) in entries {
data.extend_from_slice(b"100644 ");
data.extend_from_slice(name.as_bytes());
data.push(0);
data.extend_from_slice(id.as_bytes());
}
let tree = GitObject::new(ObjectType::Tree, data);
store.put(tree)
}
#[allow(dead_code)]
fn create_commit(
store: &ObjectStore,
tree_id: ObjectId,
parents: &[ObjectId],
message: &str,
author: &str,
) -> ObjectId {
let commit = GitObject::commit(&tree_id, parents, author, author, message);
store.put(commit)
}
fn simulate_push(repo: &Repository, objects: &[GitObject], ref_name: &str, new_id: ObjectId) {
let mut builder = PackBuilder::default();
for obj in objects {
builder.add(obj.clone());
}
let pack = builder.build().unwrap();
let old_id = ObjectId::from_hex("0000000000000000000000000000000000000000").unwrap();
let cmd_line = format!(
"{} {} {}\0report-status\n",
old_id.to_hex(),
new_id.to_hex(),
ref_name
);
let mut request = Vec::new();
{
let mut writer = PktLineWriter::new(&mut request);
writer.write(&PktLine::Data(cmd_line.into_bytes())).unwrap();
writer.flush_pkt().unwrap();
}
request.extend_from_slice(&pack);
let mut input = Cursor::new(request);
let mut output = Vec::new();
receive_pack(&mut input, &mut output, repo).unwrap();
let output_str = String::from_utf8_lossy(&output);
assert!(
output_str.contains("unpack ok"),
"Push failed: {}",
output_str
);
}
#[test]
fn test_single_node_collaboration() {
let repo = Repository::new("test-repo", "alice");
let file1_content = b"Hello from Client 1!\n";
let blob1 = GitObject::blob(file1_content.to_vec());
let blob1_id = blob1.id;
let tree1_id = create_tree(&repo.objects, &[("README.md", blob1_id)]);
let author1 = "Client1 <client1@example.com> 1700000000 +0000";
let commit1 = GitObject::commit(
&tree1_id,
&[],
author1,
author1,
"Initial commit from Client 1",
);
let commit1_id = commit1.id;
simulate_push(
&repo,
&[
blob1.clone(),
GitObject::new(ObjectType::Tree, {
let mut data = Vec::new();
data.extend_from_slice(b"100644 ");
data.extend_from_slice(b"README.md");
data.push(0);
data.extend_from_slice(blob1_id.as_bytes());
data
}),
commit1.clone(),
],
"refs/heads/main",
commit1_id,
);
assert!(repo.objects.contains(&commit1_id));
let main_ref = repo.refs.get("refs/heads/main").unwrap();
assert_eq!(main_ref.resolve(&repo.refs).unwrap(), commit1_id);
println!("Client 1 pushed: {}", commit1_id);
let refs = repo.refs.list_all();
assert!(!refs.is_empty(), "Repository should have refs after push");
let file2_content = b"Hello from Client 2!\n";
let blob2 = GitObject::blob(file2_content.to_vec());
let blob2_id = blob2.id;
let tree2_id = create_tree(
&repo.objects,
&[("README.md", blob1_id), ("client2.txt", blob2_id)],
);
let author2 = "Client2 <client2@example.com> 1700001000 +0000";
let commit2 = GitObject::commit(
&tree2_id,
&[commit1_id],
author2,
author2,
"Add file from Client 2",
);
let commit2_id = commit2.id;
simulate_push(
&repo,
&[
blob2.clone(),
GitObject::new(ObjectType::Tree, {
let mut data = Vec::new();
data.extend_from_slice(b"100644 ");
data.extend_from_slice(b"README.md");
data.push(0);
data.extend_from_slice(blob1_id.as_bytes());
data.extend_from_slice(b"100644 ");
data.extend_from_slice(b"client2.txt");
data.push(0);
data.extend_from_slice(blob2_id.as_bytes());
data
}),
commit2.clone(),
],
"refs/heads/main",
commit2_id,
);
assert!(repo.objects.contains(&commit2_id));
let main_ref = repo.refs.get("refs/heads/main").unwrap();
assert_eq!(main_ref.resolve(&repo.refs).unwrap(), commit2_id);
println!("Client 2 pushed: {}", commit2_id);
let latest = repo.refs.resolve_head().unwrap();
assert_eq!(latest, commit2_id);
let commit2_obj = repo.objects.get(&commit2_id).unwrap();
let commit2_content = String::from_utf8_lossy(&commit2_obj.data);
assert!(commit2_content.contains(&format!("parent {}", commit1_id)));
assert!(repo.objects.contains(&blob1_id));
assert!(repo.objects.contains(&blob2_id));
println!("Collaboration test passed!");
println!(" - Client 1 commit: {}", commit1_id);
println!(" - Client 2 commit: {}", commit2_id);
println!(" - Total objects: {}", repo.objects.len());
}
#[test]
fn test_ref_advertisement() {
let repo = Repository::new("test-repo", "owner");
let blob = GitObject::blob(b"test content".to_vec());
let blob_id = repo.objects.put(blob);
let tree_id = create_tree(&repo.objects, &[("file.txt", blob_id)]);
let author = "Test <test@example.com> 1700000000 +0000";
let commit = GitObject::commit(&tree_id, &[], author, author, "Test commit");
let commit_id = repo.objects.put(commit);
repo.refs.set("refs/heads/main", commit_id);
let mut output = Vec::new();
advertise_refs(&mut output, &repo, "git-upload-pack").unwrap();
let output_str = String::from_utf8_lossy(&output);
assert!(output_str.contains("git-upload-pack"));
assert!(output_str.contains(&commit_id.to_hex()));
assert!(output_str.contains("report-status"));
}
#[test]
fn test_multiple_branches() {
let repo = Repository::new("test-repo", "owner");
let blob = GitObject::blob(b"content".to_vec());
let blob_id = repo.objects.put(blob);
let tree_id = create_tree(&repo.objects, &[("file.txt", blob_id)]);
let author = "Test <test@example.com> 1700000000 +0000";
let main_commit = GitObject::commit(&tree_id, &[], author, author, "Main commit");
let main_id = repo.objects.put(main_commit);
repo.refs.set("refs/heads/main", main_id);
let blob2 = GitObject::blob(b"feature content".to_vec());
let blob2_id = repo.objects.put(blob2);
let tree2_id = create_tree(
&repo.objects,
&[("file.txt", blob_id), ("feature.txt", blob2_id)],
);
let feature_commit = GitObject::commit(&tree2_id, &[main_id], author, author, "Feature commit");
let feature_id = repo.objects.put(feature_commit);
repo.refs.set("refs/heads/feature", feature_id);
let refs = repo.refs.list("refs/heads/");
assert_eq!(refs.len(), 2);
let main_ref = repo.refs.get("refs/heads/main").unwrap();
assert_eq!(main_ref.resolve(&repo.refs).unwrap(), main_id);
let feature_ref = repo.refs.get("refs/heads/feature").unwrap();
assert_eq!(feature_ref.resolve(&repo.refs).unwrap(), feature_id);
}