fetch_source/
git.rs

1//! Support for declaring and fetching git repositories.
2
3use 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/// Represents a remote git repository to be cloned.
18#[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    /// The upstream URL.
30    pub fn upstream(&self) -> &str {
31        &self.url
32    }
33
34    /// Whether this repo will be cloned recursively.
35    pub fn is_recursive(&self) -> bool {
36        self.recursive
37    }
38
39    /// The selected branch or tag name, if any.
40    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    /// The selected commit SHA, if any.
48    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    /// Clone the repository into `dir`.
56    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}