use crate::command::{CommandExecutor, GitCommand};
use crate::error::Result;
use crate::repo::Repository;
use async_trait::async_trait;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct CloneCommand {
pub executor: CommandExecutor,
pub url: String,
pub directory: Option<PathBuf>,
pub bare: bool,
pub mirror: bool,
pub depth: Option<u32>,
pub branch: Option<String>,
pub single_branch: bool,
pub recurse_submodules: bool,
pub origin: Option<String>,
pub quiet: bool,
}
impl CloneCommand {
pub fn new(url: impl Into<String>) -> Self {
Self {
executor: CommandExecutor::default(),
url: url.into(),
directory: None,
bare: false,
mirror: false,
depth: None,
branch: None,
single_branch: false,
recurse_submodules: false,
origin: None,
quiet: false,
}
}
pub fn directory(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.directory = Some(path.into());
self
}
pub fn bare(&mut self) -> &mut Self {
self.bare = true;
self
}
pub fn mirror(&mut self) -> &mut Self {
self.mirror = true;
self
}
pub fn depth(&mut self, depth: u32) -> &mut Self {
self.depth = Some(depth);
self
}
pub fn branch(&mut self, name: impl Into<String>) -> &mut Self {
self.branch = Some(name.into());
self
}
pub fn single_branch(&mut self) -> &mut Self {
self.single_branch = true;
self
}
pub fn recurse_submodules(&mut self) -> &mut Self {
self.recurse_submodules = true;
self
}
pub fn origin(&mut self, name: impl Into<String>) -> &mut Self {
self.origin = Some(name.into());
self
}
pub fn quiet(&mut self) -> &mut Self {
self.quiet = true;
self
}
}
#[async_trait]
impl GitCommand for CloneCommand {
type Output = Repository;
fn get_executor(&self) -> &CommandExecutor {
&self.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.executor
}
fn build_command_args(&self) -> Vec<String> {
let mut args = vec!["clone".to_string()];
if self.bare {
args.push("--bare".into());
}
if self.mirror {
args.push("--mirror".into());
}
if let Some(d) = self.depth {
args.push(format!("--depth={d}"));
}
if let Some(b) = &self.branch {
args.push("--branch".into());
args.push(b.clone());
}
if self.single_branch {
args.push("--single-branch".into());
}
if self.recurse_submodules {
args.push("--recurse-submodules".into());
}
if let Some(o) = &self.origin {
args.push("--origin".into());
args.push(o.clone());
}
if self.quiet {
args.push("--quiet".into());
}
args.push(self.url.clone());
if let Some(d) = &self.directory {
args.push(d.display().to_string());
}
args
}
async fn execute(&self) -> Result<Repository> {
self.execute_raw().await?;
let dir = self
.directory
.clone()
.unwrap_or_else(|| PathBuf::from(infer_dest_dir(&self.url)));
let full = if dir.is_absolute() {
dir
} else {
self.executor
.cwd
.clone()
.map_or_else(|| dir.clone(), |c| c.join(&dir))
};
Ok(Repository::new_unchecked(full))
}
}
fn infer_dest_dir(url: &str) -> String {
let last = url.trim_end_matches('/').rsplit('/').next().unwrap_or(url);
let last = last.split(':').next_back().unwrap_or(last);
last.trim_end_matches(".git").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn infer_https_url() {
assert_eq!(infer_dest_dir("https://github.com/foo/bar.git"), "bar");
assert_eq!(infer_dest_dir("https://github.com/foo/bar"), "bar");
}
#[test]
fn infer_ssh_url() {
assert_eq!(infer_dest_dir("git@github.com:foo/bar.git"), "bar");
}
}