1use std::io::Read;
4
5use crate::error::FetchErrorKind;
6
7#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone)]
8pub enum GitReference {
9 #[serde(rename = "branch")]
10 Branch(String),
11 #[serde(rename = "tag")]
12 Tag(String),
13 #[serde(rename = "rev")]
14 Rev(String),
15}
16
17#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone)]
19pub struct Git {
20 #[serde(rename = "git")]
21 url: String,
22 #[serde(flatten)]
23 reference: Option<GitReference>,
24 #[serde(default)]
25 recursive: bool,
26}
27
28impl Git {
29 pub fn upstream(&self) -> &str {
31 &self.url
32 }
33
34 pub fn is_recursive(&self) -> bool {
36 self.recursive
37 }
38
39 pub fn branch_name(&self) -> Option<&str> {
41 match self.reference.as_ref() {
42 Some(GitReference::Branch(name)) | Some(GitReference::Tag(name)) => Some(name),
43 _ => None,
44 }
45 }
46
47 pub fn commit_sha(&self) -> Option<&str> {
49 match self.reference.as_ref() {
50 Some(GitReference::Rev(commit_sha)) => Some(commit_sha),
51 _ => None,
52 }
53 }
54
55 pub(crate) fn fetch<P: AsRef<std::path::Path>>(
57 &self,
58 dir: P,
59 ) -> Result<std::path::PathBuf, FetchErrorKind> {
60 if !dir.as_ref().exists() {
61 std::fs::create_dir_all(&dir)?;
62 }
63 let mut proc = self.clone_repo_subprocess(dir.as_ref()).spawn()?;
64 let status = proc.wait()?;
65 let full_path = dir.as_ref().to_path_buf();
66 if status.success() {
67 Ok(full_path)
68 } else {
69 let mut stderr = String::new();
70 if let Some(mut stderr_pipe) = proc.stderr.take() {
71 stderr_pipe.read_to_string(&mut stderr)?;
72 }
73 let mut command = "git clone ".to_string();
74 if let Some(branch) = self.branch_name() {
75 command.push_str(&format!("--branch {branch} "));
76 } else if let Some(commit_sha) = self.commit_sha() {
77 command.push_str(&format!("--revision {commit_sha} "));
78 }
79 if self.recursive {
80 command.push_str("--recurse-submodules --shallow-submodules");
81 }
82 command.push_str(&format!("{} {}", self.url, full_path.display()));
83 Err(FetchErrorKind::subprocess(command, status, stderr))
84 }
85 }
86
87 fn clone_repo_subprocess<P: AsRef<std::path::Path>>(&self, into: P) -> std::process::Command {
88 let mut git = std::process::Command::new("git");
89 git.args(["clone", "--depth", "1", "--no-tags"]);
90 if let Some(branch) = self.branch_name() {
91 git.args(["--branch", branch]);
92 } else if let Some(commit_sha) = self.commit_sha() {
93 git.args(["--revision", commit_sha]);
94 }
95 if self.recursive {
96 git.args(["--recurse-submodules", "--shallow-submodules"]);
97 }
98 git.arg(&self.url).arg(into.as_ref());
99 git.stdout(std::process::Stdio::piped())
100 .stderr(std::process::Stdio::piped())
101 .stdin(std::process::Stdio::null());
102 git
103 }
104}
105
106impl std::fmt::Display for Git {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 write!(f, "{}", self.url)?;
109 if let Some(reference) = &self.reference {
110 match reference {
111 GitReference::Branch(branch) => write!(f, " (branch: {branch})")?,
112 GitReference::Tag(tag) => write!(f, " (tag: {tag})")?,
113 GitReference::Rev(rev) => write!(f, " (rev: {rev})")?,
114 }
115 }
116 if self.recursive {
117 write!(f, " [recursive]")?;
118 }
119 Ok(())
120 }
121}