use git2::{Repository, Signature};
use quorum_core::git::{diff_for_source, DiffSource, FileStatus};
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn sig() -> Signature<'static> {
Signature::now("range-diff-test", "range-diff@test.local").unwrap()
}
fn commit_all(repo: &Repository, msg: &str) -> String {
let mut index = repo.index().unwrap();
index
.add_all(["*"], git2::IndexAddOption::DEFAULT, None)
.unwrap();
index.write().unwrap();
let tree_oid = index.write_tree().unwrap();
let tree = repo.find_tree(tree_oid).unwrap();
let parent = repo.head().ok().and_then(|h| h.peel_to_commit().ok());
let parents: Vec<&git2::Commit> = parent.iter().collect();
let oid = repo
.commit(Some("HEAD"), &sig(), &sig(), msg, &tree, &parents)
.unwrap();
oid.to_string()
}
fn write(path: &Path, contents: &str) {
if let Some(p) = path.parent() {
fs::create_dir_all(p).unwrap();
}
fs::write(path, contents).unwrap();
}
#[test]
fn staged_index_unchanged_from_phase_1a() {
let td = TempDir::new().unwrap();
let repo = Repository::init(td.path()).unwrap();
repo.config().unwrap().set_str("user.name", "rd").unwrap();
repo.config()
.unwrap()
.set_str("user.email", "rd@test.local")
.unwrap();
write(&td.path().join("hello.txt"), "v1\n");
commit_all(&repo, "init");
write(&td.path().join("hello.txt"), "v2\n");
write(&td.path().join("new.txt"), "added\n");
let mut idx = repo.index().unwrap();
idx.add_all(["*"], git2::IndexAddOption::DEFAULT, None)
.unwrap();
idx.write().unwrap();
let (_repo, staged) = diff_for_source(td.path(), &DiffSource::StagedIndex).unwrap();
assert!(!staged.is_empty);
let names: Vec<_> = staged.files.iter().map(|f| f.path.as_str()).collect();
assert!(names.contains(&"hello.txt"), "modified file present");
assert!(names.contains(&"new.txt"), "new file present");
assert!(staged.unified.contains("hello.txt"));
assert!(staged.unified.contains("+v2"));
}
#[test]
fn commit_range_diff_walks_a_two_commit_history() {
let td = TempDir::new().unwrap();
let repo = Repository::init(td.path()).unwrap();
repo.config().unwrap().set_str("user.name", "rd").unwrap();
repo.config()
.unwrap()
.set_str("user.email", "rd@test.local")
.unwrap();
write(&td.path().join("keep.txt"), "stable\n");
write(&td.path().join("delete-me.txt"), "doomed\n");
let base = commit_all(&repo, "base");
write(&td.path().join("keep.txt"), "stable plus added line\n");
write(&td.path().join("new-feature.rs"), "fn main() {}\n");
fs::remove_file(td.path().join("delete-me.txt")).unwrap();
let head = commit_all(&repo, "head");
let (_repo, diff) = diff_for_source(
td.path(),
&DiffSource::CommitRange {
base: base.clone(),
head: head.clone(),
},
)
.unwrap();
assert!(!diff.is_empty);
let by_path: std::collections::HashMap<&str, &quorum_core::git::StagedFile> =
diff.files.iter().map(|f| (f.path.as_str(), f)).collect();
let keep = by_path.get("keep.txt").expect("keep.txt in diff");
assert_eq!(keep.status, FileStatus::Modified);
let blob = String::from_utf8(keep.index_blob.clone().unwrap()).unwrap();
assert!(
blob.contains("stable plus added line"),
"head-tree blob carries the head version, not base"
);
let new_file = by_path.get("new-feature.rs").expect("new file in diff");
assert_eq!(new_file.status, FileStatus::Added);
let blob = String::from_utf8(new_file.index_blob.clone().unwrap()).unwrap();
assert!(blob.contains("fn main"));
let deleted = by_path.get("delete-me.txt").expect("deleted file in diff");
assert_eq!(deleted.status, FileStatus::Deleted);
assert!(deleted.index_blob.is_none());
assert!(diff.unified.contains("+stable plus added line"));
assert!(diff.unified.contains("delete-me.txt"));
}
#[test]
fn commit_range_with_invalid_rev_errors() {
let td = TempDir::new().unwrap();
let repo = Repository::init(td.path()).unwrap();
repo.config().unwrap().set_str("user.name", "rd").unwrap();
repo.config()
.unwrap()
.set_str("user.email", "rd@test.local")
.unwrap();
write(&td.path().join("a.txt"), "x\n");
commit_all(&repo, "one");
let err = diff_for_source(
td.path(),
&DiffSource::CommitRange {
base: "deadbeefcafebabe".into(),
head: "HEAD".into(),
},
);
assert!(
err.is_err(),
"unknown base ref should propagate as GitError"
);
}
#[test]
fn commit_range_empty_when_no_diff_between_revs() {
let td = TempDir::new().unwrap();
let repo = Repository::init(td.path()).unwrap();
repo.config().unwrap().set_str("user.name", "rd").unwrap();
repo.config()
.unwrap()
.set_str("user.email", "rd@test.local")
.unwrap();
write(&td.path().join("a.txt"), "x\n");
let only = commit_all(&repo, "only");
let (_repo, diff) = diff_for_source(
td.path(),
&DiffSource::CommitRange {
base: only.clone(),
head: only,
},
)
.unwrap();
assert!(diff.is_empty);
assert!(diff.files.is_empty());
}