#![allow(dead_code)]
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
pub fn unique_path(prefix: &str) -> PathBuf {
let pid = std::process::id();
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base = std::env::temp_dir().join("gitwell-tests");
let _ = fs::create_dir_all(&base);
base.join(format!("{}-{}-{}-{}", prefix, pid, nanos, n))
}
pub struct TempRepo {
path: PathBuf,
cleanup: bool,
}
impl TempRepo {
pub fn new(name: &str) -> Self {
let path = unique_path(name);
fs::create_dir_all(&path).expect("create temp repo dir");
run_git(&path, &["init", "-q", "-b", "main"]);
run_git(&path, &["config", "user.email", "test@gitwell.test"]);
run_git(&path, &["config", "user.name", "GitWell Test"]);
run_git(&path, &["config", "commit.gpgsign", "false"]);
fs::write(path.join("README.md"), "# test\n").expect("write README");
run_git(&path, &["add", "README.md"]);
run_git(&path, &["commit", "-q", "-m", "Initial commit"]);
TempRepo { path, cleanup: true }
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn keep(mut self) -> Self {
self.cleanup = false;
self
}
pub fn write_file(&self, rel: &str, contents: &str) {
let p = self.path.join(rel);
if let Some(parent) = p.parent() {
fs::create_dir_all(parent).expect("create parent dir");
}
fs::write(p, contents).expect("write file");
}
pub fn append_file(&self, rel: &str, contents: &str) {
let p = self.path.join(rel);
let mut existing = fs::read_to_string(&p).unwrap_or_default();
existing.push_str(contents);
fs::write(p, existing).expect("append file");
}
pub fn commit(&self, msg: &str) {
run_git(&self.path, &["add", "-A"]);
run_git(
&self.path,
&["commit", "-q", "--allow-empty", "-m", msg],
);
}
pub fn commit_file(&self, rel: &str, contents: &str, msg: &str) {
self.write_file(rel, contents);
self.commit(msg);
}
pub fn commit_file_at(&self, rel: &str, contents: &str, msg: &str, unix_ts: i64) {
self.write_file(rel, contents);
run_git(&self.path, &["add", "-A"]);
let date = format!("@{} +0000", unix_ts);
run_git_env(
&self.path,
&["commit", "-q", "--allow-empty", "-m", msg],
&[
("GIT_AUTHOR_DATE", &date),
("GIT_COMMITTER_DATE", &date),
],
);
}
pub fn empty_commit_at(&self, msg: &str, unix_ts: i64) {
let date = format!("@{} +0000", unix_ts);
run_git_env(
&self.path,
&["commit", "-q", "--allow-empty", "-m", msg],
&[
("GIT_AUTHOR_DATE", &date),
("GIT_COMMITTER_DATE", &date),
],
);
}
pub fn branch(&self, name: &str) {
run_git(&self.path, &["checkout", "-q", "-b", name]);
}
pub fn checkout(&self, name: &str) {
run_git(&self.path, &["checkout", "-q", name]);
}
pub fn merge(&self, name: &str) {
run_git(
&self.path,
&["merge", "-q", "--no-edit", "--no-ff", name],
);
}
pub fn tag(&self, name: &str) {
run_git(&self.path, &["tag", name]);
}
pub fn stash(&self, file_rel: &str, contents: &str, msg: &str) {
self.write_file(file_rel, contents);
run_git(&self.path, &["add", file_rel]);
run_git(&self.path, &["stash", "push", "-q", "-m", msg]);
}
pub fn reset_hard(&self, target: &str) {
run_git(&self.path, &["reset", "--hard", "-q", target]);
}
pub fn rev_parse(&self, refname: &str) -> String {
let out = Command::new("git")
.arg("-C")
.arg(&self.path)
.args(["rev-parse", refname])
.output()
.expect("git rev-parse");
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
}
impl Drop for TempRepo {
fn drop(&mut self) {
if self.cleanup && self.path.exists() {
let _ = fs::remove_dir_all(&self.path);
}
}
}
pub struct TempDir {
path: PathBuf,
repos: Vec<TempRepo>,
cleanup: bool,
}
impl TempDir {
pub fn new(name: &str) -> Self {
let path = unique_path(name);
fs::create_dir_all(&path).expect("create temp dir");
TempDir {
path,
repos: Vec::new(),
cleanup: true,
}
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn add_repo(&mut self, name: &str) -> &TempRepo {
let repo_path = self.path.join(name);
fs::create_dir_all(&repo_path).expect("create child repo dir");
run_git(&repo_path, &["init", "-q", "-b", "main"]);
run_git(&repo_path, &["config", "user.email", "test@gitwell.test"]);
run_git(&repo_path, &["config", "user.name", "GitWell Test"]);
run_git(&repo_path, &["config", "commit.gpgsign", "false"]);
fs::write(repo_path.join("README.md"), "# test\n").expect("write README");
run_git(&repo_path, &["add", "README.md"]);
run_git(&repo_path, &["commit", "-q", "-m", "Initial commit"]);
let repo = TempRepo {
path: repo_path,
cleanup: false,
};
self.repos.push(repo);
self.repos.last().expect("just pushed")
}
pub fn repos(&self) -> &[TempRepo] {
&self.repos
}
}
impl Drop for TempDir {
fn drop(&mut self) {
if self.cleanup && self.path.exists() {
let _ = fs::remove_dir_all(&self.path);
}
}
}
fn run_git(cwd: &Path, args: &[&str]) {
let status = Command::new("git")
.arg("-C")
.arg(cwd)
.args(args)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.unwrap_or_else(|e| panic!("failed to spawn git {:?}: {}", args, e));
if !status.success() {
panic!("git {:?} in {} failed: {}", args, cwd.display(), status);
}
}
fn run_git_env(cwd: &Path, args: &[&str], env: &[(&str, &str)]) {
let mut cmd = Command::new("git");
cmd.arg("-C").arg(cwd).args(args);
for (k, v) in env {
cmd.env(k, v);
}
let status = cmd
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.unwrap_or_else(|e| panic!("failed to spawn git {:?}: {}", args, e));
if !status.success() {
panic!("git {:?} in {} failed: {}", args, cwd.display(), status);
}
}