use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use crate::{GitCredentials, GitRepo, GitRepoCloneRequest, GitRepoInfo};
use git_url_parse::GitUrl;
use color_eyre::eyre::{eyre, Result};
use tracing::{debug, info};
impl GitRepoCloneRequest {
pub fn new<S: AsRef<str>>(url: S) -> Result<Self> {
let url = if let Ok(u) = GitUrl::parse(url.as_ref()) {
u
} else {
return Err(eyre!("url failed to parse as GitUrl"));
};
Ok(Self {
url,
credentials: None,
head: None,
branch: None,
path: None,
})
}
pub fn with_path(mut self, path: PathBuf) -> Result<Self> {
self.path = if let Ok(p) = fs::canonicalize(path) {
Some(p)
} else {
return Err(eyre!("Directory was not found"));
};
Ok(self)
}
pub fn with_branch(mut self, branch: Option<String>) -> Self {
if let Some(b) = branch {
self.branch = Some(b);
}
self
}
pub fn with_credentials(mut self, creds: Option<GitCredentials>) -> Self {
self.credentials = creds;
self
}
pub fn to_repo(&self) -> GitRepo {
self.into()
}
pub fn to_info(&self) -> GitRepoInfo {
self.into()
}
pub fn git_clone<P: AsRef<Path>>(&self, target: P) -> Result<GitRepo> {
let git_info: GitRepoInfo = self.into();
let cb = git_info.build_git2_remotecallback()?;
let mut builder = git2::build::RepoBuilder::new();
let mut fetch_options = git2::FetchOptions::new();
fetch_options.remote_callbacks(cb);
builder.fetch_options(fetch_options);
if let Some(b) = &self.branch {
builder.branch(b);
}
let repo = match builder.clone(&self.url.to_string(), target.as_ref()) {
Ok(repo) => repo,
Err(e) => return Err(eyre!("failed to clone: {}", e)),
};
let mut git_repo: GitRepo = repo.try_into()?;
git_repo = git_repo.with_credentials(self.credentials.clone());
Ok(git_repo)
}
pub fn git_clone_shallow<P: AsRef<Path>>(&self, target: P) -> Result<GitRepo> {
let repo = if let Some(cred) = self.credentials.clone() {
match cred {
crate::GitCredentials::SshKey {
username,
public_key,
private_key,
passphrase,
} => {
let mut parsed_uri = self.url.trim_auth();
parsed_uri.user = Some(username.to_string());
let privkey_path =
if let Ok(path) = private_key.clone().into_os_string().into_string() {
path
} else {
return Err(eyre!("Couldn't convert path to string"));
};
let shell_clone_command = if let Ok(spawn) = Command::new("git")
.arg("clone")
.arg(format!("{}", parsed_uri))
.arg(format!("{}", target.as_ref().display()))
.arg("--no-single-branch")
.arg("--depth=1")
.arg("--config")
.arg(format!("core.sshcommand=ssh -i {privkey_path}"))
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
spawn
} else {
return Err(eyre!("failed to run git clone"));
};
let clone_out = if let Ok(wait) = shell_clone_command.wait_with_output() {
wait
} else {
return Err(eyre!("failed to open stdout"));
};
debug!("Clone output: {:?}", clone_out);
let creds = GitCredentials::SshKey {
username,
public_key,
private_key,
passphrase,
};
if let Ok(repo) = GitRepo::open(target.as_ref().to_path_buf(), None, None) {
repo
} else {
return Err(eyre!("Failed to open shallow clone dir: {:?}", clone_out));
}
.with_credentials(Some(creds))
}
crate::GitCredentials::UserPassPlaintext { username, password } => {
let mut cli_remote_url = self.url.clone();
cli_remote_url.user = Some(username.to_string());
cli_remote_url.token = Some(password.to_string());
let shell_clone_command = if let Ok(spawn) = Command::new("git")
.arg("clone")
.arg(format!("{}", cli_remote_url))
.arg(format!("{}", target.as_ref().display()))
.arg("--no-single-branch")
.arg("--depth=1")
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
spawn
} else {
return Err(eyre!("Failed to run git clone"));
};
let clone_out = if let Some(stdout) = shell_clone_command.stdout {
stdout
} else {
return Err(eyre!("Failed to open stdout"));
};
let creds = GitCredentials::UserPassPlaintext { username, password };
if let Ok(repo) = GitRepo::open(target.as_ref().to_path_buf(), None, None) {
repo
} else {
return Err(eyre!("Failed to open shallow clone dir: {:?}", clone_out));
}
.with_credentials(Some(creds))
}
}
} else {
let parsed_uri = self.url.trim_auth();
info!("Url: {}", format!("{}", parsed_uri));
info!("Directory: {}", format!("{}", target.as_ref().display()));
let shell_clone_command = if let Ok(spawn) = Command::new("git")
.arg("clone")
.arg(format!("{}", parsed_uri))
.arg(format!("{}", target.as_ref().display()))
.arg("--no-single-branch")
.arg("--depth=1")
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
spawn
} else {
return Err(eyre!("Failed to run git clone"));
};
let clone_out = if let Ok(stdout) = shell_clone_command.wait_with_output() {
stdout
} else {
return Err(eyre!("Failed to wait for output"));
}
.stdout;
if let Ok(repo) = GitRepo::open(target.as_ref().to_path_buf(), None, None) {
repo
} else {
return Err(eyre!("Failed to open shallow clone dir: {:?}", clone_out));
}
};
Ok(repo)
}
}