use std::io::Read;
use crate::error::FetchErrorKind;
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone)]
pub enum GitReference {
#[serde(rename = "branch")]
Branch(String),
#[serde(rename = "tag")]
Tag(String),
#[serde(rename = "rev")]
Rev(String),
}
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone)]
pub struct Git {
#[serde(rename = "git")]
url: String,
#[serde(flatten)]
reference: Option<GitReference>,
#[serde(default)]
recursive: bool,
}
impl Git {
pub fn upstream(&self) -> &str {
&self.url
}
pub fn is_recursive(&self) -> bool {
self.recursive
}
pub fn branch_name(&self) -> Option<&str> {
match self.reference.as_ref() {
Some(GitReference::Branch(name)) | Some(GitReference::Tag(name)) => Some(name),
_ => None,
}
}
pub fn commit_sha(&self) -> Option<&str> {
match self.reference.as_ref() {
Some(GitReference::Rev(commit_sha)) => Some(commit_sha),
_ => None,
}
}
pub(crate) fn fetch<P: AsRef<std::path::Path>>(
&self,
dir: P,
) -> Result<std::path::PathBuf, FetchErrorKind> {
if !dir.as_ref().exists() {
std::fs::create_dir_all(&dir)?;
}
let mut proc = self.clone_repo_subprocess(dir.as_ref()).spawn()?;
let status = proc.wait()?;
let full_path = dir.as_ref().to_path_buf();
if status.success() {
Ok(full_path)
} else {
let mut stderr = String::new();
if let Some(mut stderr_pipe) = proc.stderr.take() {
stderr_pipe.read_to_string(&mut stderr)?;
}
let mut command = "git clone ".to_string();
if let Some(branch) = self.branch_name() {
command.push_str(&format!("--branch {branch} "));
} else if let Some(commit_sha) = self.commit_sha() {
command.push_str(&format!("--revision {commit_sha} "));
}
if self.recursive {
command.push_str("--recurse-submodules --shallow-submodules");
}
command.push_str(&format!("{} {}", self.url, full_path.display()));
Err(FetchErrorKind::subprocess(command, status, stderr))
}
}
fn clone_repo_subprocess<P: AsRef<std::path::Path>>(&self, into: P) -> std::process::Command {
let mut git = std::process::Command::new("git");
git.args(["clone", "--depth", "1", "--no-tags"]);
if let Some(branch) = self.branch_name() {
git.args(["--branch", branch]);
} else if let Some(commit_sha) = self.commit_sha() {
git.args(["--revision", commit_sha]);
}
if self.recursive {
git.args(["--recurse-submodules", "--shallow-submodules"]);
}
git.arg(&self.url).arg(into.as_ref());
git.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.stdin(std::process::Stdio::null());
git
}
}
impl std::fmt::Display for Git {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.url)?;
if let Some(reference) = &self.reference {
match reference {
GitReference::Branch(branch) => write!(f, " (branch: {branch})")?,
GitReference::Tag(tag) => write!(f, " (tag: {tag})")?,
GitReference::Rev(rev) => write!(f, " (rev: {rev})")?,
}
}
if self.recursive {
write!(f, " [recursive]")?;
}
Ok(())
}
}