use super::*;
use crate::test_support::{fixture, git, git_text, remove, repository};
use std::path::{Path, PathBuf};
fn names_of_every_length() -> Vec<String> {
return (1..=18).map(|length| { return "n".repeat(length); }).collect();
}
fn populated_repository(root: &Path) -> PathBuf {
let repo: PathBuf = repository(root, "repo");
let deep: PathBuf = repo.join("d".repeat(200)).join("e".repeat(200));
std::fs::create_dir_all(&deep).expect("deep directories");
std::fs::write(deep.join("deep.txt"), "deep\n").expect("deep file");
for name in names_of_every_length() {
std::fs::write(repo.join(&name), format!("{name}\n")).expect("file");
}
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=files"]);
return repo;
}
fn read(repo: &Path) -> IndexFlags {
let bytes: Vec<u8> = std::fs::read(repo.join(".git").join("index")).expect("index file");
return read_index_flags(bytes.as_slice());
}
fn git_says_marked(repo: &Path) -> bool {
let listing: String = git_text(repo, &["ls-files", "-v"]);
return listing.lines().any(|line| {
return line.chars().next().is_some_and(|tag| { return tag.is_ascii_lowercase() || tag == 'S'; });
});
}
fn assert_agrees(repo: &Path, expected_marked: bool) {
assert_eq!(git_says_marked(repo), expected_marked, "the scenario sets what it says it sets");
let expected: IndexFlags = if expected_marked { IndexFlags::Marked } else { IndexFlags::Plain };
assert_eq!(read(repo), expected);
}
#[test]
fn an_unmarked_index_is_plain_in_every_version() {
let root: PathBuf = fixture("index-flags-plain");
let repo: PathBuf = populated_repository(root.as_path());
assert_agrees(repo.as_path(), false);
for version in ["3", "4", "2"] {
git(repo.as_path(), &["update-index", "--index-version", version]);
assert_agrees(repo.as_path(), false);
}
remove(root.as_path());
}
#[test]
fn an_assume_unchanged_entry_marks_the_index() {
let root: PathBuf = fixture("index-flags-assume");
let repo: PathBuf = populated_repository(root.as_path());
let names: Vec<String> = names_of_every_length();
let candidates: [&str; 3] = [names[0].as_str(), names[names.len() / 2].as_str(), names[names.len() - 1].as_str()];
for version in ["2", "4"] {
git(repo.as_path(), &["update-index", "--index-version", version]);
for name in candidates {
git(repo.as_path(), &["update-index", "--assume-unchanged", "--", name]);
assert_agrees(repo.as_path(), true);
git(repo.as_path(), &["update-index", "--no-assume-unchanged", "--", name]);
assert_agrees(repo.as_path(), false);
}
}
remove(root.as_path());
}
fn git_says_intent_to_add(repo: &Path) -> bool {
return git_text(repo, &["status", "--porcelain"]).lines().any(|line| { return line.starts_with(" A "); });
}
#[test]
fn an_intent_to_add_entry_marks_the_index() {
let root: PathBuf = fixture("index-flags-intent");
let repo: PathBuf = populated_repository(root.as_path());
std::fs::write(repo.join("added.txt"), "new\n").expect("new file");
git(repo.as_path(), &["add", "--intent-to-add", "--", "added.txt"]);
assert!(git_says_intent_to_add(repo.as_path()), "the scenario sets what it says it sets");
for version in ["3", "4"] {
git(repo.as_path(), &["update-index", "--index-version", version]);
assert_eq!(read(repo.as_path()), IndexFlags::Marked, "version {version}");
}
git(repo.as_path(), &["add", "--", "added.txt"]);
assert!(!git_says_intent_to_add(repo.as_path()));
assert_eq!(read(repo.as_path()), IndexFlags::Plain);
remove(root.as_path());
}
#[test]
fn a_skip_worktree_entry_marks_the_index() {
let root: PathBuf = fixture("index-flags-skip");
let repo: PathBuf = populated_repository(root.as_path());
for version in ["3", "4"] {
git(repo.as_path(), &["update-index", "--index-version", version]);
git(repo.as_path(), &["update-index", "--skip-worktree", "--", "n"]);
assert_agrees(repo.as_path(), true);
git(repo.as_path(), &["update-index", "--no-skip-worktree", "--", "n"]);
assert_agrees(repo.as_path(), false);
}
remove(root.as_path());
}
#[test]
fn a_sha256_index_is_read_with_its_longer_object_names() {
let root: PathBuf = fixture("index-flags-sha256");
let repo: PathBuf = root.join("repo");
std::fs::create_dir(&repo).expect("repo directory");
git(repo.as_path(), &["init", "--quiet", "--object-format=sha256"]);
for name in names_of_every_length() {
std::fs::write(repo.join(&name), format!("{name}\n")).expect("file");
}
git(repo.as_path(), &["add", "--all"]);
assert_agrees(repo.as_path(), false);
git(repo.as_path(), &["update-index", "--assume-unchanged", "--", "nn"]);
assert_agrees(repo.as_path(), true);
remove(root.as_path());
}
#[test]
fn a_split_index_is_unreadable() {
let root: PathBuf = fixture("index-flags-split");
let repo: PathBuf = populated_repository(root.as_path());
git(repo.as_path(), &["update-index", "--split-index"]);
assert_eq!(read(repo.as_path()), IndexFlags::Unreadable);
remove(root.as_path());
}
#[test]
fn an_empty_index_is_plain_and_damaged_bytes_are_unreadable() {
let root: PathBuf = fixture("index-flags-damaged");
let repo: PathBuf = populated_repository(root.as_path());
let whole: Vec<u8> = std::fs::read(repo.join(".git").join("index")).expect("index file");
git(repo.as_path(), &["read-tree", "--empty"]);
assert_eq!(read(repo.as_path()), IndexFlags::Plain);
assert_eq!(read_index_flags(&[]), IndexFlags::Unreadable);
assert_eq!(read_index_flags(b"not an index at all, just text"), IndexFlags::Unreadable);
for cut in [whole.len() - 1, whole.len() / 2, HEADER_LENGTH] {
assert_eq!(read_index_flags(&whole[..cut]), IndexFlags::Unreadable, "cut at {cut}");
}
let mut wrong_version: Vec<u8> = whole.clone();
wrong_version[7] = 9;
assert_eq!(read_index_flags(wrong_version.as_slice()), IndexFlags::Unreadable);
remove(root.as_path());
}