#![cfg(feature = "git")]
mod common;
use prollytree::diff::TakeSourceResolver;
use prollytree::git::versioned_store::{
GitVersionedKvStore, HistoricalAccess, HistoricalCommitAccess,
};
#[test]
fn test_full_branching_lifecycle() {
let (_temp, dataset) = common::setup_repo_and_dataset();
let mut store = GitVersionedKvStore::<32>::init(&dataset).unwrap();
store
.insert(b"main_k".to_vec(), b"main_v".to_vec())
.unwrap();
store.commit("main data").unwrap();
store.create_branch("feature").unwrap();
store.checkout("feature").unwrap();
store
.insert(b"feat_k".to_vec(), b"feat_v".to_vec())
.unwrap();
store.commit("feature data").unwrap();
assert!(store.get(b"main_k").is_some());
assert!(store.get(b"feat_k").is_some());
store.checkout("main").unwrap();
assert!(store.get(b"main_k").is_some());
assert!(
store.get(b"feat_k").is_none(),
"main should not have feature-only key"
);
std::mem::forget(_temp);
}
#[test]
fn test_multiple_branches_diverge_and_converge() {
let (_temp, dataset) = common::setup_repo_and_dataset();
let mut store = GitVersionedKvStore::<32>::init(&dataset).unwrap();
store.insert(b"base".to_vec(), b"val".to_vec()).unwrap();
store.commit("base").unwrap();
for i in 0..3 {
let branch = format!("branch{i}");
store.create_branch(&branch).unwrap();
store.checkout(&branch).unwrap();
store
.insert(
format!("b{i}_key").into_bytes(),
format!("b{i}_val").into_bytes(),
)
.unwrap();
store.commit(&format!("branch {i} commit")).unwrap();
store.checkout("main").unwrap();
}
for i in 0..3 {
let branch = format!("branch{i}");
store
.merge(&branch, &TakeSourceResolver)
.expect(&format!("merge {branch}"));
}
assert!(store.get(b"base").is_some());
for i in 0..3 {
assert!(
store.get(format!("b{i}_key").as_bytes()).is_some(),
"b{i}_key missing after merge"
);
}
std::mem::forget(_temp);
}
#[test]
fn test_history_tracks_key_across_commits() {
let (_temp, dataset) = common::setup_repo_and_dataset();
let mut store = GitVersionedKvStore::<32>::init(&dataset).unwrap();
for i in 0..5 {
store
.insert(b"tracked".to_vec(), format!("v{i}").into_bytes())
.unwrap();
store.commit(&format!("update {i}")).unwrap();
}
let history = store.get_commits_for_key(b"tracked").unwrap();
assert!(
history.len() >= 5,
"expected at least 5 commits for tracked key, got {}",
history.len()
);
std::mem::forget(_temp);
}
#[test]
fn test_commit_does_not_stage_unrelated_repo_root_file() {
let (temp, dataset) = common::setup_repo_and_dataset();
let mut store = GitVersionedKvStore::<32>::init(&dataset).unwrap();
std::fs::write(temp.path().join("unrelated.txt"), "keep me out").unwrap();
store
.insert(b"dataset_k".to_vec(), b"dataset_v".to_vec())
.unwrap();
let commit_id = store.commit("dataset-only commit").unwrap();
let output = std::process::Command::new("git")
.args(["ls-tree", "-r", "--name-only", &commit_id.to_string()])
.current_dir(temp.path())
.output()
.expect("git ls-tree");
assert!(
output.status.success(),
"git ls-tree failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let tree_entries = String::from_utf8_lossy(&output.stdout);
assert!(
tree_entries
.lines()
.any(|line| line.starts_with("dataset/")),
"commit should still include dataset entries, got:\n{tree_entries}"
);
assert!(
!tree_entries.lines().any(|line| line == "unrelated.txt"),
"commit tree should not include unrelated repo-root file, got:\n{tree_entries}"
);
}
#[test]
fn test_get_keys_at_historical_ref() {
let (_temp, dataset) = common::setup_repo_and_dataset();
let mut store = GitVersionedKvStore::<32>::init(&dataset).unwrap();
store.insert(b"A".to_vec(), b"a1".to_vec()).unwrap();
let c1 = store.commit("commit 1").unwrap();
store.insert(b"B".to_vec(), b"b1".to_vec()).unwrap();
let c2 = store.commit("commit 2").unwrap();
store.insert(b"C".to_vec(), b"c1".to_vec()).unwrap();
store.commit("commit 3").unwrap();
let keys_c1 = store.get_keys_at_ref(&c1.to_string()).unwrap();
assert!(keys_c1.contains_key(b"A".as_ref()));
assert!(
!keys_c1.contains_key(b"B".as_ref()),
"B should not exist at c1"
);
let keys_c2 = store.get_keys_at_ref(&c2.to_string()).unwrap();
assert!(keys_c2.contains_key(b"A".as_ref()));
assert!(keys_c2.contains_key(b"B".as_ref()));
assert!(
!keys_c2.contains_key(b"C".as_ref()),
"C should not exist at c2"
);
std::mem::forget(_temp);
}
#[test]
fn test_open_after_close_preserves_state() {
let (_temp, dataset) = common::setup_repo_and_dataset();
{
let mut store = GitVersionedKvStore::<32>::init(&dataset).unwrap();
for i in 0..20 {
store
.insert(
format!("pk{i:03}").into_bytes(),
format!("pv{i:03}").into_bytes(),
)
.unwrap();
}
store.commit("persist data").unwrap();
}
{
let store = GitVersionedKvStore::<32>::open(&dataset).unwrap();
for i in 0..20 {
assert_eq!(
store.get(format!("pk{i:03}").as_bytes()),
Some(format!("pv{i:03}").into_bytes()),
"pk{i:03} should persist"
);
}
let log = store.log().unwrap();
assert!(log.len() >= 2, "should have initial + persist commits");
}
}