use gix_error::ResultExt;
use crate::{Result, bstr::BString, remote};
#[cfg(feature = "async-network-client")]
use gix_transport::client::async_io::Transport;
#[cfg(feature = "blocking-network-client")]
use gix_transport::client::blocking_io::Transport;
type ConfigureRemoteFn = Box<dyn FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>>>;
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
type ConfigureConnectionFn = Box<dyn FnMut(&mut remote::Connection<'_, '_, '_, Box<dyn Transport + Send>>) -> Result>;
#[must_use]
pub struct PrepareFetch {
repo: Option<crate::Repository>,
remote_name: Option<BString>,
config_overrides: Vec<BString>,
configure_remote: Option<ConfigureRemoteFn>,
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
configure_connection: Option<ConfigureConnectionFn>,
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
fetch_options: remote::ref_map::Options,
#[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))]
url: gix_url::Url,
#[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))]
shallow: remote::fetch::Shallow,
#[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))]
ref_name: Option<gix_ref::PartialName>,
#[cfg_attr(not(feature = "blocking-network-client"), allow(dead_code))]
revision: Option<gix_refspec::RefSpec>,
remove_worktree_on_drop: bool,
}
pub mod with_revision {
#[derive(Debug)]
#[expect(missing_docs)]
pub enum Error {
Parse(gix_error::Error),
Invalid { revision: crate::bstr::BString },
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Parse(err) => err.fmt(f),
Error::Invalid { revision } => write!(
f,
"A clone revision must be HEAD, a full reference name, or a full object ID, got {revision:?}"
),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Parse(err) => Some(err),
Error::Invalid { .. } => Some(const { &gix_error::ClassificationMarker::VALIDATION }),
}
}
}
impl From<gix_error::Exn<gix_error::Message>> for Error {
fn from(err: gix_error::Exn<gix_error::Message>) -> Self {
Error::Parse(err.into_error())
}
}
}
impl PrepareFetch {
pub fn new<Url, E>(
url: Url,
path: impl AsRef<std::path::Path>,
kind: crate::create::Kind,
create_opts: crate::create::Options,
open_opts: crate::open::Options,
) -> Result<Self>
where
Url: TryInto<gix_url::Url, Error = E>,
E: std::error::Error + Send + Sync + 'static,
{
Self::new_inner(url.try_into().or_error()?, path.as_ref(), kind, create_opts, open_opts)
}
fn new_inner(
mut url: gix_url::Url,
path: &std::path::Path,
kind: crate::create::Kind,
mut create_opts: crate::create::Options,
open_opts: crate::open::Options,
) -> Result<Self> {
let mut open_opts = open_opts.without_repository_environment_overrides();
if create_opts.destination_must_be_empty.is_none() {
create_opts.destination_must_be_empty = Some(true);
}
let git_dir = match kind {
crate::create::Kind::WithWorktree => path.join(gix_discover::DOT_GIT_DIR),
crate::create::Kind::Bare => path.to_owned(),
};
let config = crate::config(Some(&git_dir), &open_opts)?;
if crate::config::cache::util::config_bool_opt(
&config,
&crate::config::tree::Core::SYMLINKS,
"core.symlinks",
open_opts.lenient_config,
)? == Some(false)
{
open_opts.api_config_overrides.push("core.symlinks=false".into());
}
let remove_worktree_on_drop = match std::fs::read_dir(path) {
Ok(mut entries) => entries.next().is_none(),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => true,
Err(_) => false,
};
let mut repo = crate::ThreadSafeRepository::init_opts(path, kind, create_opts, open_opts)?.to_thread_local();
url.canonicalize(repo.options.current_dir_or_empty()).or_raise(|| {
gix_error::message!(
"Failed to turn the relative file url {:?} into an absolute one",
url.to_bstring()
)
})?;
repo.committer_or_set_generic_fallback()?;
Ok(PrepareFetch {
url,
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
fetch_options: Default::default(),
repo: Some(repo),
config_overrides: Vec::new(),
remote_name: None,
configure_remote: None,
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
configure_connection: None,
shallow: remote::fetch::Shallow::NoChange,
ref_name: None,
revision: None,
remove_worktree_on_drop,
})
}
}
#[must_use]
#[cfg(feature = "worktree-mutation")]
#[derive(Debug)]
pub struct PrepareCheckout {
pub(self) repo: Option<crate::Repository>,
pub(self) ref_name: Option<gix_ref::PartialName>,
pub(self) remove_worktree_on_drop: bool,
}
fn cleanup_clone_destination_on_drop(repo: &crate::Repository, remove_worktree_on_drop: bool) {
let path_to_remove = if remove_worktree_on_drop {
Some(repo.workdir().unwrap_or_else(|| repo.path()))
} else {
None
};
if let Some(path_to_remove) = path_to_remove {
std::fs::remove_dir_all(path_to_remove).ok();
}
}
#[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
mod access_feat {
use super::Transport;
use crate::Result;
use crate::clone::PrepareFetch;
impl PrepareFetch {
pub fn configure_connection(
mut self,
f: impl FnMut(&mut crate::remote::Connection<'_, '_, '_, Box<dyn Transport + Send>>) -> Result + 'static,
) -> Self {
self.configure_connection = Some(Box::new(f));
self
}
pub fn with_fetch_options(mut self, opts: crate::remote::ref_map::Options) -> Self {
self.fetch_options = opts;
self
}
}
}
#[cfg(any(feature = "async-network-client-async-std", feature = "blocking-network-client"))]
pub mod fetch;
mod access;
#[cfg(feature = "worktree-mutation")]
pub mod checkout;