use std::path::{Path, PathBuf};
use std::process::Command;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum GitError {
#[error("git command failed: {0}")]
Command(String),
#[error("io: {0}")]
Io(#[from] std::io::Error),
}
pub fn clone_or_fetch(url: &str, dest: &Path) -> Result<(), GitError> {
if dest.exists() && dest.join(".git").exists() {
run_git(dest, ["fetch", "--tags", "--prune", "--force"])?;
Ok(())
} else {
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
run_git(
&PathBuf::from("."),
[
"clone",
"--quiet",
"--no-checkout",
url,
&dest.to_string_lossy(),
],
)?;
Ok(())
}
}
pub fn checkout(repo: &Path, gitref: &str) -> Result<(), GitError> {
run_git(repo, ["checkout", "--quiet", "--detach", gitref])
}
pub fn head_sha(repo: &Path) -> Result<String, GitError> {
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(["rev-parse", "HEAD"])
.output()?;
if !out.status.success() {
return Err(GitError::Command(
String::from_utf8_lossy(&out.stderr).into_owned(),
));
}
Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
fn run_git<'a, A>(cwd: &Path, args: A) -> Result<(), GitError>
where
A: IntoIterator<Item = &'a str>,
{
let out = Command::new("git").current_dir(cwd).args(args).output()?;
if out.status.success() {
Ok(())
} else {
Err(GitError::Command(
String::from_utf8_lossy(&out.stderr).into_owned(),
))
}
}