use std::{
path::{Path, PathBuf},
sync::mpsc,
};
use git2::{
AutotagOption, CertificateCheckStatus, Error, FetchOptions, Remote, RemoteCallbacks,
build::RepoBuilder,
};
use crate::GitRepository;
#[derive(Clone)]
pub struct CloneConfig {
pub(crate) clone_dir_name: String,
pub(crate) parent_path: PathBuf,
pub(crate) url: String,
pub(crate) sender: Option<mpsc::Sender<(usize, String)>>,
pub(crate) flags: CloneFlagsInternal,
pub(crate) skip_owner_validation: bool,
pub(crate) bypass_certificate_check: bool,
}
impl CloneConfig {
pub fn new(url: impl Into<String>, parent_dir: &Path) -> Self {
let target_dir: String = url.into();
let url = target_dir.clone();
let target_dir = target_dir.split("/").into_iter().last().unwrap();
let target_dir = match target_dir.strip_suffix(".git") {
Some(t) => t,
None => target_dir,
};
CloneConfig {
clone_dir_name: target_dir.to_string(),
parent_path: parent_dir.to_path_buf(),
url: url.into(),
flags: CloneFlagsInternal::default(),
sender: None,
skip_owner_validation: false,
bypass_certificate_check: false,
}
}
pub fn get_skip_owner_validation(&self) -> bool {
return self.skip_owner_validation;
}
pub fn get_url(&self) -> &str {
return &self.url;
}
pub fn get_bypass_certificate_check(&self) -> bool {
return self.bypass_certificate_check;
}
pub fn get_update_channel(&mut self) -> mpsc::Receiver<(usize, String)> {
let (sender, receiver) = mpsc::channel();
self.sender = Some(sender);
return receiver;
}
pub fn get_parent_path(&self) -> PathBuf {
return self.parent_path.clone();
}
pub fn get_clone_dir_name(&self) -> String {
if self.flags.bare {
let mut dir = self.clone_dir_name.clone();
dir = dir + ".git";
return dir;
}
return self.clone_dir_name.clone();
}
pub fn skip_owner_validation(&mut self, skip: bool) {
self.skip_owner_validation = skip;
}
pub fn bypass_certificate_check(&mut self, bypass: bool) {
self.bypass_certificate_check = bypass;
}
pub fn custom_clone_directory(&mut self, dir: impl Into<String>) {
self.clone_dir_name = dir.into();
}
pub fn add_flag(&mut self, flag: CloneFlags) {
match flag {
CloneFlags::Branch(branch) => self.flags.branch = branch,
CloneFlags::Depth(depth) => self.flags.depth = depth,
CloneFlags::SingleBranch(single) => self.flags.single_branch = single,
CloneFlags::Bare(bare) => self.flags.bare = bare,
}
}
}
#[derive(Clone, PartialEq, PartialOrd)]
pub(crate) struct CloneFlagsInternal {
pub(crate) branch: Option<String>, pub(crate) depth: Option<usize>, pub(crate) single_branch: bool, pub(crate) bare: bool,
}
impl Default for CloneFlagsInternal {
fn default() -> Self {
CloneFlagsInternal {
branch: None,
depth: None,
single_branch: false,
bare: false,
}
}
}
pub enum CloneFlags {
Branch(Option<String>),
Depth(Option<usize>),
SingleBranch(bool),
Bare(bool),
}
impl GitRepository {
pub fn git_clone(&mut self, config: CloneConfig) -> Result<(), Error> {
if self.repository.is_some() {
return Ok(());
}
let mut fetch_options = FetchOptions::new();
let mut repo_builder = RepoBuilder::new();
let mut callbacks = RemoteCallbacks::new();
let mut callbacks2 = RemoteCallbacks::new();
let mut remote = Remote::create_detached(config.url.clone())?;
if config.skip_owner_validation {
unsafe {
git2::opts::set_verify_owner_validation(false)?;
};
}
if config.bypass_certificate_check {
callbacks.certificate_check(|_, _| Ok(CertificateCheckStatus::CertificateOk));
}
let cred = self.cred.clone();
let cred2 = self.cred.clone();
callbacks.credentials(move |_a: &str, _b, _c| {
return cred.get_cred();
});
callbacks2.credentials(move |_a: &str, _b, _c| {
return cred2.get_cred();
});
let remote = remote.connect_auth(git2::Direction::Fetch, Some(callbacks2), None)?;
let mut def_branch: Vec<u8> = vec![];
remote.default_branch()?.clone_into(&mut def_branch);
let def_branch = String::from_utf8(def_branch);
let mut def_branch = def_branch.unwrap_or("main".to_string());
def_branch = def_branch.split("/").last().unwrap_or("main").to_string();
let repo_path = config.get_parent_path().join(config.get_clone_dir_name());
if let Some(branch) = &config.flags.branch {
def_branch = branch.to_string();
}
if let Some(depth) = config.flags.depth {
let depth: i32 = depth as i32;
fetch_options.depth(depth);
fetch_options.download_tags(AutotagOption::None);
let branch = def_branch.clone();
repo_builder.remote_create(move |repo, name, url| {
let refspec = format!("+refs/heads/{0:}:refs/remotes/origin/{0:}", branch);
repo.remote_with_fetch(name, url, &refspec)
});
}
if config.flags.single_branch {
fetch_options.download_tags(AutotagOption::None);
let branch = def_branch.clone();
repo_builder.remote_create(move |repo, name, url| {
let refspec = format!("+refs/heads/{0:}:refs/remotes/origin/{0:}", branch);
repo.remote_with_fetch(name, url, &refspec)
});
}
let repo_builder = repo_builder.bare(config.flags.bare);
let repo_builder = repo_builder.branch(&def_branch);
fetch_options.remote_callbacks(callbacks);
let repo_builder = repo_builder.fetch_options(fetch_options);
self.repository = Some(repo_builder.clone(config.get_url(), &repo_path)?);
Ok(())
}
}