#![cfg(unix)]
use super::CandidateStore;
use super::tests::{at, paths, rev_parse, staged_changes, store, write};
use crate::candidate_error::{CandidateError, CandidateFailure};
use crate::candidate_object::{CandidateMode, ObjectId};
use crate::candidate_record::CandidateChange;
use crate::candidate_version::{Candidate, CandidateSource, CandidateVersion};
use crate::test_support::{fixture, git, remove, repository};
use std::path::PathBuf;
use std::rc::Rc;
#[test]
fn committed_version_lists_the_commit_delta() {
let root: PathBuf = fixture("store-committed");
let repo: PathBuf = staged_changes(root.as_path());
git(repo.as_path(), &["commit", "--quiet", "--message=changes"]);
write(repo.as_path(), b"modify.txt", b"later worktree edit\n");
git(repo.as_path(), &["add", "modify.txt"]);
let landed: ObjectId = rev_parse(repo.as_path(), "HEAD");
let base: ObjectId = rev_parse(repo.as_path(), "HEAD~1");
let mut subject: CandidateStore = store(repo.as_path());
let version: Rc<CandidateVersion> = subject
.version(&CandidateSource::Committed(landed))
.expect("committed version");
assert_eq!(version.candidates().len(), 10);
assert_eq!(
at(&version, b"modify.txt").change,
CandidateChange::Modified
);
assert_eq!(
&subject.bytes(at(&version, b"modify.txt")).expect("bytes")[..],
b"after\n"
);
assert_eq!(at(&version, b"delete.txt").change, CandidateChange::Deleted);
assert_eq!(at(&version, b"sub").mode, CandidateMode::Gitlink);
let base_version: Rc<CandidateVersion> = subject
.version(&CandidateSource::Committed(base))
.expect("base version");
assert_eq!(base_version.candidates().len(), 6);
assert_eq!(
&subject
.bytes(at(&base_version, b"modify.txt"))
.expect("bytes")[..],
b"before\n"
);
remove(root.as_path());
}
#[test]
fn root_and_merge_commits_are_listed() {
let root: PathBuf = fixture("store-merge");
let repo: PathBuf = root.join("repo");
std::fs::create_dir(&repo).expect("repository directory");
git(
repo.as_path(),
&["init", "--quiet", "--initial-branch=main"],
);
write(repo.as_path(), b"base.txt", b"base\n");
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=root"]);
let first: ObjectId = rev_parse(repo.as_path(), "HEAD");
git(repo.as_path(), &["checkout", "--quiet", "-b", "side"]);
write(repo.as_path(), b"side.txt", b"side\n");
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=side"]);
git(repo.as_path(), &["checkout", "--quiet", "main"]);
write(repo.as_path(), b"main.txt", b"main\n");
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=main"]);
git(repo.as_path(), &["merge", "--quiet", "--no-edit", "side"]);
let merge: ObjectId = rev_parse(repo.as_path(), "HEAD");
let mut subject: CandidateStore = store(repo.as_path());
let root_version: Rc<CandidateVersion> = subject
.version(&CandidateSource::Committed(first))
.expect("root version");
assert_eq!(paths(&root_version), [b"base.txt".to_vec()]);
assert_eq!(
at(&root_version, b"base.txt").change,
CandidateChange::Added
);
let merge_version: Rc<CandidateVersion> = subject
.version(&CandidateSource::Committed(merge))
.expect("merge version");
assert_eq!(
paths(&merge_version),
[b"main.txt".to_vec(), b"side.txt".to_vec()]
);
assert_eq!(
&subject
.bytes(at(&merge_version, b"side.txt"))
.expect("bytes")[..],
b"side\n"
);
remove(root.as_path());
}
#[test]
fn invalidation_retires_versions_and_their_candidates() {
let root: PathBuf = fixture("store-invalidate");
let repo: PathBuf = repository(root.as_path(), "repo");
write(repo.as_path(), b"fixed.txt", b"before the fix\n");
write(repo.as_path(), b"same.txt", b"untouched\n");
git(repo.as_path(), &["add", "--all"]);
let mut subject: CandidateStore = store(repo.as_path());
let before: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("first version");
let stale: Candidate = at(&before, b"fixed.txt").clone();
assert_eq!(stale.identity.generation, 0);
assert_eq!(
&subject.bytes(&stale).expect("bytes")[..],
b"before the fix\n"
);
write(repo.as_path(), b"fixed.txt", b"after the fix\n");
git(repo.as_path(), &["add", "fixed.txt"]);
let reused: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("reused version");
assert!(Rc::ptr_eq(&before, &reused));
assert_eq!(
&subject.bytes(at(&reused, b"fixed.txt")).expect("bytes")[..],
b"before the fix\n"
);
subject.invalidate();
let error: CandidateError = subject.bytes(&stale).expect_err("stale candidate");
assert_eq!(error.failure, CandidateFailure::StaleCandidate);
assert!(
error
.message
.starts_with("cli-git refused to read a candidate"),
"{error}"
);
let after: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("second version");
assert!(!Rc::ptr_eq(&before, &after));
let fresh: &Candidate = at(&after, b"fixed.txt");
assert_eq!(fresh.identity.generation, 1);
assert_ne!(fresh.object, stale.object);
assert_eq!(
&subject.bytes(fresh).expect("bytes")[..],
b"after the fix\n"
);
assert_eq!(
at(&after, b"same.txt").object,
at(&before, b"same.txt").object
);
assert_eq!(
&subject.bytes(at(&after, b"same.txt")).expect("bytes")[..],
b"untouched\n"
);
subject.invalidate();
let third: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("third version");
assert_eq!(at(&third, b"fixed.txt").identity.generation, 2);
assert_eq!(
subject.bytes(fresh).expect_err("stale").failure,
CandidateFailure::StaleCandidate
);
remove(root.as_path());
}
#[test]
fn versions_are_kept_per_source() {
let root: PathBuf = fixture("store-sources");
let repo: PathBuf = repository(root.as_path(), "repo");
write(repo.as_path(), b"one.txt", b"one\n");
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=one"]);
let landed: ObjectId = rev_parse(repo.as_path(), "HEAD");
let base: ObjectId = rev_parse(repo.as_path(), "HEAD~1");
write(repo.as_path(), b"two.txt", b"two\n");
git(repo.as_path(), &["add", "--all"]);
let mut subject: CandidateStore = store(repo.as_path());
let staged: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("staged");
let against_base: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstCommit(base))
.expect("staged against base");
let committed: Rc<CandidateVersion> = subject
.version(&CandidateSource::Committed(landed.clone()))
.expect("committed");
assert_eq!(paths(&staged), [b"two.txt".to_vec()]);
assert_eq!(
paths(&against_base),
[b"one.txt".to_vec(), b"two.txt".to_vec()]
);
assert_eq!(paths(&committed), [b"one.txt".to_vec()]);
assert!(Rc::ptr_eq(
&committed,
&subject
.version(&CandidateSource::Committed(landed))
.expect("committed again")
));
assert!(Rc::ptr_eq(
&staged,
&subject
.version(&CandidateSource::StagedAgainstHead)
.expect("staged again")
));
remove(root.as_path());
}