git_spawn/command/
clone.rs1use crate::command::{CommandExecutor, GitCommand};
4use crate::error::Result;
5use crate::repo::Repository;
6use async_trait::async_trait;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone)]
11pub struct CloneCommand {
12 pub executor: CommandExecutor,
14 pub url: String,
16 pub directory: Option<PathBuf>,
18 pub bare: bool,
20 pub mirror: bool,
22 pub depth: Option<u32>,
24 pub branch: Option<String>,
26 pub single_branch: bool,
28 pub recurse_submodules: bool,
30 pub origin: Option<String>,
32 pub quiet: bool,
34}
35
36impl CloneCommand {
37 pub fn new(url: impl Into<String>) -> Self {
39 Self {
40 executor: CommandExecutor::default(),
41 url: url.into(),
42 directory: None,
43 bare: false,
44 mirror: false,
45 depth: None,
46 branch: None,
47 single_branch: false,
48 recurse_submodules: false,
49 origin: None,
50 quiet: false,
51 }
52 }
53
54 pub fn directory(&mut self, path: impl Into<PathBuf>) -> &mut Self {
56 self.directory = Some(path.into());
57 self
58 }
59
60 pub fn bare(&mut self) -> &mut Self {
62 self.bare = true;
63 self
64 }
65
66 pub fn mirror(&mut self) -> &mut Self {
68 self.mirror = true;
69 self
70 }
71
72 pub fn depth(&mut self, depth: u32) -> &mut Self {
74 self.depth = Some(depth);
75 self
76 }
77
78 pub fn branch(&mut self, name: impl Into<String>) -> &mut Self {
80 self.branch = Some(name.into());
81 self
82 }
83
84 pub fn single_branch(&mut self) -> &mut Self {
86 self.single_branch = true;
87 self
88 }
89
90 pub fn recurse_submodules(&mut self) -> &mut Self {
92 self.recurse_submodules = true;
93 self
94 }
95
96 pub fn origin(&mut self, name: impl Into<String>) -> &mut Self {
98 self.origin = Some(name.into());
99 self
100 }
101
102 pub fn quiet(&mut self) -> &mut Self {
104 self.quiet = true;
105 self
106 }
107}
108
109#[async_trait]
110impl GitCommand for CloneCommand {
111 type Output = Repository;
112
113 fn get_executor(&self) -> &CommandExecutor {
114 &self.executor
115 }
116
117 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
118 &mut self.executor
119 }
120
121 fn build_command_args(&self) -> Vec<String> {
122 let mut args = vec!["clone".to_string()];
123 if self.bare {
124 args.push("--bare".into());
125 }
126 if self.mirror {
127 args.push("--mirror".into());
128 }
129 if let Some(d) = self.depth {
130 args.push(format!("--depth={d}"));
131 }
132 if let Some(b) = &self.branch {
133 args.push("--branch".into());
134 args.push(b.clone());
135 }
136 if self.single_branch {
137 args.push("--single-branch".into());
138 }
139 if self.recurse_submodules {
140 args.push("--recurse-submodules".into());
141 }
142 if let Some(o) = &self.origin {
143 args.push("--origin".into());
144 args.push(o.clone());
145 }
146 if self.quiet {
147 args.push("--quiet".into());
148 }
149 args.push(self.url.clone());
150 if let Some(d) = &self.directory {
151 args.push(d.display().to_string());
152 }
153 args
154 }
155
156 async fn execute(&self) -> Result<Repository> {
157 self.execute_raw().await?;
158 let dir = self
159 .directory
160 .clone()
161 .unwrap_or_else(|| PathBuf::from(infer_dest_dir(&self.url)));
162 let full = if dir.is_absolute() {
163 dir
164 } else {
165 self.executor
166 .cwd
167 .clone()
168 .map_or_else(|| dir.clone(), |c| c.join(&dir))
169 };
170 Ok(Repository::new_unchecked(full))
171 }
172}
173
174fn infer_dest_dir(url: &str) -> String {
175 let last = url.trim_end_matches('/').rsplit('/').next().unwrap_or(url);
176 let last = last.split(':').next_back().unwrap_or(last);
177 last.trim_end_matches(".git").to_string()
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn infer_https_url() {
186 assert_eq!(infer_dest_dir("https://github.com/foo/bar.git"), "bar");
187 assert_eq!(infer_dest_dir("https://github.com/foo/bar"), "bar");
188 }
189
190 #[test]
191 fn infer_ssh_url() {
192 assert_eq!(infer_dest_dir("git@github.com:foo/bar.git"), "bar");
193 }
194}