use anyhow::{Context, Result};
use git2::{FetchOptions, RemoteCallbacks, build::RepoBuilder};
use log::{debug, info};
use std::path::PathBuf;
use tempfile::TempDir;
pub struct ClonedRepo {
pub temp_dir: TempDir,
pub path: PathBuf,
}
impl ClonedRepo {
pub fn path(&self) -> &PathBuf {
&self.path
}
}
pub fn clone_repository(url: &str, branch: Option<&str>) -> Result<ClonedRepo> {
info!("Cloning repository: {}", url);
let temp_dir = TempDir::new().context("Failed to create temporary directory for git clone")?;
let clone_path = temp_dir.path().to_path_buf();
debug!("Clone target: {}", clone_path.display());
let mut callbacks = RemoteCallbacks::new();
callbacks.transfer_progress(|progress| {
if progress.received_objects() == progress.total_objects() {
debug!(
"Resolving deltas: {}/{}",
progress.indexed_deltas(),
progress.total_deltas()
);
} else {
debug!(
"Receiving objects: {}/{} ({} bytes)",
progress.received_objects(),
progress.total_objects(),
progress.received_bytes()
);
}
true
});
let mut fetch_opts = FetchOptions::new();
fetch_opts.remote_callbacks(callbacks);
let mut builder = RepoBuilder::new();
builder.fetch_options(fetch_opts);
if let Some(branch_name) = branch {
debug!("Checking out branch: {}", branch_name);
builder.branch(branch_name);
}
builder
.clone(url, &clone_path)
.with_context(|| format!("Failed to clone repository: {}", url))?;
info!("Clone complete: {}", clone_path.display());
Ok(ClonedRepo {
temp_dir,
path: clone_path,
})
}
pub fn repo_name_from_url(url: &str) -> Option<String> {
let path = if url.contains("://") {
url.rsplit('/').next()?
} else if url.contains(':') {
url.rsplit(':').next()?.rsplit('/').next()?
} else {
return None;
};
let name = path.strip_suffix(".git").unwrap_or(path);
if name.is_empty() {
None
} else {
Some(name.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_repo_name_from_https_url() {
assert_eq!(
repo_name_from_url("https://github.com/user/myrepo.git"),
Some("myrepo".to_string())
);
assert_eq!(
repo_name_from_url("https://github.com/user/myrepo"),
Some("myrepo".to_string())
);
assert_eq!(
repo_name_from_url("https://gitlab.com/group/subgroup/project.git"),
Some("project".to_string())
);
}
#[test]
fn test_repo_name_from_ssh_url() {
assert_eq!(
repo_name_from_url("git@github.com:user/myrepo.git"),
Some("myrepo".to_string())
);
assert_eq!(
repo_name_from_url("git@github.com:user/myrepo"),
Some("myrepo".to_string())
);
}
#[test]
fn test_repo_name_invalid_url() {
assert_eq!(repo_name_from_url("not-a-url"), None);
assert_eq!(repo_name_from_url(""), None);
}
}