use assert_cmd::cargo::cargo_bin;
use bstr::{BString, ByteSlice};
use duct::cmd;
use pretty_assertions::assert_str_eq;
use regex::Regex;
use std::ffi::OsString;
use std::fs;
use std::path::Path;
pub fn git_status_vars<I, S>(root: &Path, args: I) -> BString
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
let executable = cargo_bin(env!("CARGO_PKG_NAME"));
cmd(executable, args)
.dir(root)
.env("HOME", root)
.env("GIT_CONFIG_GLOBAL", root.join(".gitconfig"))
.env("GIT_CONFIG_SYSTEM", "/dev/null")
.stderr_to_stdout()
.stdout_capture()
.run()
.unwrap()
.stdout
.into()
}
fn run_git<I, S>(root: &Path, repo: &str, args: I) -> duct::Expression
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
cmd("git", args)
.dir(root.join(repo))
.env("HOME", root)
.env("GIT_CONFIG_GLOBAL", root.join(".gitconfig"))
.env("GIT_CONFIG_SYSTEM", "/dev/null")
.stderr_to_stdout()
.stdout_capture()
}
pub fn git<I, S>(root: &Path, repo: &str, args: I) -> std::io::Result<()>
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
let args: Vec<OsString> = args.into_iter().map(Into::into).collect();
let shell_args =
shell_words::join(args.iter().map(|arg| arg.to_string_lossy()));
println!("`git {shell_args}` in {}", root.join(repo).display());
let output = run_git(root, repo, args).run()?;
print!("{}", output.stdout.as_bstr());
Ok(())
}
pub fn prepare_root(root: &Path) {
fs::write(
root.join(".gitconfig"),
"[user]\n\
name = Name\n\
email = name@example.com\n\
[init]\n\
defaultBranch = main\n\
[advice]\n\
detachedHead = false\n\
skippedCherryPicks = false\n",
)
.unwrap();
}
pub fn git_init(root: &Path, name: &str) {
git(root, ".", ["init", name]).unwrap();
}
pub fn make_commit(root: &Path, repo: &str, n: u8) {
fs::write(root.join(repo).join("a"), format!("{n}a")).unwrap();
fs::write(root.join(repo).join("b"), format!("{n}b")).unwrap();
git(root, repo, ["add", "a", "b"]).unwrap();
git(root, repo, ["commit", "-m", &format!("commit {n}")]).unwrap();
}
pub fn assert_git_status_vars(root: &Path, repo: &str, expected: &str) {
let re = Regex::new(r"_hash=[0-9a-f]{40}").unwrap();
let output = git_status_vars(root, [repo]);
let output = output.to_str_lossy();
let output = re.replace_all(&output, "_hash=@HASH@");
let expected = strip_indent(expected)
.replace("@REPO@", &path_to_string(&root.join(repo)));
assert_str_eq!(expected, output);
}
pub fn strip_indent(input: &str) -> String {
input
.strip_prefix('\n')
.map(|rest| rest.trim_start_matches(' ')) .map(|rest| {
#[allow(clippy::arithmetic_side_effects)]
let prefix_len = input.len() - rest.len();
if prefix_len > 1 {
let newline_indent = &input[..prefix_len];
rest.replace(newline_indent, "\n")
} else {
String::from(rest)
}
})
.unwrap_or_else(|| input.to_owned())
}
fn path_to_string(path: &Path) -> String {
if std::path::MAIN_SEPARATOR == '/' {
path.display().to_string()
} else {
path.display()
.to_string()
.replace(std::path::MAIN_SEPARATOR, "/")
}
}