use super::git::git;
use super::payload::Binding;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
pub const BRANCH: &str = "balls";
fn run(cwd: &Path, args: &[&str]) {
git(cwd, args).unwrap();
}
fn identify(repo: &Path) {
run(repo, &["config", "user.name", "test"]);
run(repo, &["config", "user.email", "test@example.com"]);
}
pub fn commit(repo: &Path, file: &str, content: &str) -> String {
fs::write(repo.join(file), format!("{content}\n")).unwrap();
run(repo, &["add", "-A"]);
run(repo, &["commit", "-q", "-m", content]);
git(repo, &["rev-parse", "HEAD"]).unwrap()
}
pub fn tip(repo: &Path, rev: &str) -> String {
git(repo, &["rev-parse", rev]).unwrap()
}
pub fn empty_remote(tmp: &Path) -> PathBuf {
static N: AtomicU64 = AtomicU64::new(0);
let remote = tmp.join(format!("remote-{}.git", N.fetch_add(1, Ordering::Relaxed)));
run(tmp, &["init", "--bare", "-q", "-b", BRANCH, &remote.to_string_lossy()]);
remote
}
pub fn unpushable_remote(tmp: &Path) -> PathBuf {
use std::os::unix::fs::PermissionsExt;
let remote = empty_remote(tmp);
let hook = remote.join("hooks").join("pre-receive");
fs::write(&hook, "#!/bin/sh\nexit 1\n").unwrap();
fs::set_permissions(&hook, fs::Permissions::from_mode(0o755)).unwrap();
remote
}
pub fn remote_with_config(tmp: &Path, store_branch: &str) -> PathBuf {
let remote = empty_remote(tmp);
let seed = tmp.join(format!("{}-cfgseed", remote.file_name().unwrap().to_string_lossy()));
run(tmp, &["clone", "-q", &remote.to_string_lossy(), &seed.to_string_lossy()]);
identify(&seed);
run(&seed, &["checkout", "-q", "--orphan", crate::LANDING_BRANCH]);
fs::create_dir_all(seed.join("config")).unwrap();
fs::write(seed.join("config/balls.toml"), format!("tasks_branch = \"{store_branch}\"\n")).unwrap();
run(&seed, &["add", "-A"]);
run(&seed, &["commit", "-q", "-m", "config"]);
run(&seed, &["push", "-q", "origin", crate::LANDING_BRANCH]);
remote
}
pub fn remote_with_branch(tmp: &Path) -> PathBuf {
let remote = empty_remote(tmp);
let seed = tmp.join("seed");
run(tmp, &["clone", "-q", &remote.to_string_lossy(), &seed.to_string_lossy()]);
identify(&seed);
fs::create_dir_all(seed.join("tasks")).unwrap();
fs::write(seed.join("tasks/.gitkeep"), "").unwrap();
commit(&seed, "seed.txt", "seed");
run(&seed, &["push", "-q", "origin", BRANCH]);
remote
}
pub fn legacy_remote(tmp: &Path) -> PathBuf {
let remote = empty_remote(tmp);
let seed = tmp.join(format!("{}-legacy", remote.file_name().unwrap().to_string_lossy()));
run(tmp, &["clone", "-q", &remote.to_string_lossy(), &seed.to_string_lossy()]);
identify(&seed);
fs::create_dir_all(seed.join(".balls/tasks")).unwrap();
fs::write(seed.join(".balls/tasks/bl-aaaa.json"), "{\"id\":\"bl-aaaa\"}\n").unwrap();
commit(&seed, ".balls/config.json", "legacy store");
run(&seed, &["push", "-q", "origin", BRANCH]);
remote
}
pub fn checkout(tmp: &Path, remote: &Path, name: &str) -> PathBuf {
let dest = tmp.join(name);
run(tmp, &["clone", "-q", &remote.to_string_lossy(), &dest.to_string_lossy()]);
identify(&dest);
dest
}
pub fn store_clone(tmp: &Path, remote: &Path) -> PathBuf {
checkout(tmp, remote, "store")
}
pub fn local_unpushed(tmp: &Path) -> PathBuf {
let store = tmp.join("store");
run(tmp, &["init", "-q", "-b", BRANCH, &store.to_string_lossy()]);
identify(&store);
commit(&store, "seed.txt", "seed");
store
}
pub fn landing_repo(tmp: &Path) -> PathBuf {
static N: AtomicU64 = AtomicU64::new(0);
let landing = tmp.join(format!("landing-{}", N.fetch_add(1, Ordering::Relaxed)));
run(tmp, &["init", "-q", "-b", "main", &landing.to_string_lossy()]);
identify(&landing);
commit(&landing, "config.txt", "config");
landing
}
pub fn env(home: &Path, state: &Path) -> super::Env {
super::Env {
xdg: crate::layout::Xdg::with(home, None, Some(&state.to_string_lossy())),
}
}
pub fn binding(remote: Option<&Path>, store: &Path) -> Binding {
Binding {
remote: remote.map(|r| r.to_string_lossy().into_owned()),
stealth: false,
tasks_branch: BRANCH.to_string(),
store: store.to_string_lossy().into_owned(),
landing: String::new(),
invocation_path: store.to_string_lossy().into_owned(),
}
}
pub fn default_binding(remote: Option<&Path>, store: &Path) -> Binding {
Binding { tasks_branch: crate::DEFAULT_TASKS_BRANCH.to_string(), ..binding(remote, store) }
}
pub fn tracked(remote: &Path, store: &Path, landing: &Path) -> Binding {
Binding { landing: landing.to_string_lossy().into_owned(), ..binding(Some(remote), store) }
}