#![allow(clippy::expect_used)]
use std::path::Path;
use std::path::PathBuf;
use tempfile::TempDir;
use crate::PreEditImage;
use crate::WorkspaceStore;
use crate::scope::Gitignore;
use crate::scope::HiddenFiles;
use crate::scope::is_ignored;
use crate::scope::load_ignore;
pub struct Fixture {
data: TempDir,
workspace: TempDir,
}
impl Default for Fixture {
fn default() -> Self {
Self::new()
}
}
impl Fixture {
pub fn new() -> Self {
Self {
data: TempDir::new().expect("temp data dir"),
workspace: TempDir::new().expect("temp workspace"),
}
}
pub fn data_dir(&self) -> &Path {
self.data.path()
}
pub fn workspace(&self) -> &Path {
self.workspace.path()
}
pub fn store(&self) -> WorkspaceStore {
WorkspaceStore::open(self.data_dir(), self.workspace()).expect("open store")
}
pub fn write(&self, rel: &str, content: impl AsRef<[u8]>) -> PathBuf {
let path = self.workspace().join(rel);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create parent");
}
std::fs::write(&path, content).expect("write file");
path
}
pub fn read(&self, rel: &str) -> String {
std::fs::read_to_string(self.workspace().join(rel)).expect("read file")
}
pub fn exists(&self, rel: &str) -> bool {
self.workspace().join(rel).exists()
}
pub fn remove(&self, rel: &str) {
std::fs::remove_file(self.workspace().join(rel)).expect("remove file");
}
pub fn path(&self, rel: &str) -> PathBuf {
self.workspace().join(rel)
}
pub fn all_files(&self) -> Vec<PathBuf> {
fn walk(dir: &Path, ignore: &ignore::gitignore::Gitignore, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
if entry.file_name().to_string_lossy().starts_with('.') {
continue;
}
let path = entry.path();
if is_ignored(ignore, &path) {
continue;
}
if path.is_dir() {
walk(&path, ignore, out);
} else if path.is_file() {
out.push(path);
}
}
}
let mut out = Vec::new();
walk(self.workspace(), &load_ignore(self.workspace()), &mut out);
out.sort();
out
}
pub fn restore_scope(&self, session: &str) -> Vec<PathBuf> {
let store = self.store();
let mut files = self.all_files();
files.extend(
store
.tracked_paths(session)
.expect("tracked paths")
.into_iter()
.map(PathBuf::from),
);
files
}
pub fn protection(&self) -> Gitignore {
load_ignore(self.workspace())
}
pub fn capture(&self, session: &str, turn: &str) -> crate::Checkpoint {
self.store()
.checkpoint(session, turn, self.all_files())
.expect("checkpoint")
}
pub fn declare(&self, session: &str, turn: &str, rel: &str) {
let path = self.path(rel);
let image = match std::fs::read(&path) {
Ok(bytes) => PreEditImage::Existed(bytes),
Err(_) => PreEditImage::DidNotExist,
};
self.store()
.attach_pre_edit(session, turn, &path.to_string_lossy(), &image)
.expect("attach pre-edit");
}
pub fn age_store(&self) {
age_store(self.data_dir());
}
pub fn tracked_set(&self, already_known: Vec<PathBuf>) -> Vec<PathBuf> {
crate::scope::tracked_files(
&[self.workspace().to_path_buf()],
already_known,
HiddenFiles::Skip,
crate::ScanLimits::default(),
)
.files
.into_iter()
.collect()
}
}
pub fn age_store(data_dir: &Path) {
fn walk(dir: &Path, when: std::time::SystemTime) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, when);
} else if let Ok(file) = std::fs::File::options().write(true).open(&path) {
let _ = file.set_times(std::fs::FileTimes::new().set_modified(when));
}
}
}
walk(
data_dir,
std::time::SystemTime::now() - std::time::Duration::from_secs(3600),
);
}
pub fn blob_count(data_dir: &Path) -> usize {
fn walk(dir: &Path, seen: &mut usize) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk(&path, seen);
} else {
*seen += 1;
}
}
}
let Ok(root) = crate::workspace::store_root(data_dir) else {
return 0;
};
let mut seen = 0;
walk(&crate::workspace::blobs_dir(&root), &mut seen);
seen
}
pub fn no_rules() -> Gitignore {
Gitignore::empty()
}
pub fn rules_for(root: &Path, pattern: &str) -> Gitignore {
let mut builder = crate::scope::GitignoreBuilder::new(root);
builder
.add_line(None, pattern)
.expect("valid ignore pattern");
builder.build().expect("build ignore rules")
}