use crate::{
bstr::{BString, ByteSlice},
clone::PrepareFetch,
};
use gix_ref::Category;
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
Connect(#[from] crate::remote::connect::Error),
#[error(transparent)]
PrepareFetch(#[from] crate::remote::fetch::prepare::Error),
#[error(transparent)]
Fetch(#[from] crate::remote::fetch::Error),
#[error(transparent)]
RemoteInit(#[from] crate::remote::init::Error),
#[error("Custom configuration of remote to clone from failed")]
RemoteConfiguration(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("Custom configuration of connection to use when cloning failed")]
RemoteConnection(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
RemoteName(#[from] crate::config::remote::symbolic_name::Error),
#[error(transparent)]
ParseConfig(#[from] crate::config::overrides::Error),
#[error(transparent)]
ApplyConfig(#[from] crate::config::Error),
#[error("Failed to load repo-local git configuration before writing")]
LoadConfig(#[from] gix_config::file::init::from_paths::Error),
#[error("Failed to store configured remote in memory")]
SaveConfig(#[from] crate::remote::save::AsError),
#[error("Failed to write repository configuration to disk")]
SaveConfigIo(#[from] std::io::Error),
#[error("The remote HEAD points to a reference named {head_ref_name:?} which is invalid.")]
InvalidHeadRef {
source: gix_validate::reference::name::Error,
head_ref_name: crate::bstr::BString,
},
#[error("Failed to update HEAD with values from remote")]
HeadUpdate(#[from] crate::reference::edit::Error),
#[error("The remote didn't have any ref that matched '{}'", wanted.as_ref().as_bstr())]
RefNameMissing { wanted: gix_ref::PartialName },
#[error("The remote has {} refs for '{}', try to use a specific name: {}", candidates.len(), wanted.as_ref().as_bstr(), candidates.iter().filter_map(|n| n.to_str().ok()).collect::<Vec<_>>().join(", "))]
RefNameAmbiguous {
wanted: gix_ref::PartialName,
candidates: Vec<BString>,
},
#[error(transparent)]
CommitterOrFallback(#[from] crate::config::time::Error),
#[error(transparent)]
RefMap(#[from] crate::remote::ref_map::Error),
#[error(transparent)]
ReferenceName(#[from] gix_validate::reference::name::Error),
}
impl PrepareFetch {
#[gix_protocol::maybe_async::maybe_async]
pub async fn fetch_only<P>(
&mut self,
mut progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(crate::Repository, crate::remote::fetch::Outcome), Error>
where
P: crate::NestedProgress,
P::SubProgress: 'static,
{
use crate::{bstr::ByteVec, remote, remote::fetch::RefLogMessage};
let repo = self
.repo
.as_mut()
.expect("user error: multiple calls are allowed only until it succeeds");
repo.committer_or_set_generic_fallback()?;
if !self.config_overrides.is_empty() {
let mut snapshot = repo.config_snapshot_mut();
snapshot.append_config(&self.config_overrides, gix_config::Source::Api)?;
}
let remote_name = match self.remote_name.as_ref() {
Some(name) => name.to_owned(),
None => repo
.config
.resolved
.string(crate::config::tree::Clone::DEFAULT_REMOTE_NAME)
.map(|n| crate::config::tree::Clone::DEFAULT_REMOTE_NAME.try_into_symbolic_name(n))
.transpose()?
.unwrap_or_else(|| "origin".into()),
};
let mut remote = repo.remote_at(self.url.clone())?;
let use_single_branch_for_shallow = self.shallow != remote::fetch::Shallow::NoChange
&& remote.fetch_specs.is_empty()
&& self.fetch_options.extra_refspecs.is_empty();
let target_ref = if use_single_branch_for_shallow {
if let Some(ref_name) = &self.ref_name {
Some(Category::LocalBranch.to_full_name(ref_name.as_ref().as_bstr())?)
} else {
let prev_tags = std::mem::replace(&mut remote.fetch_tags, remote::fetch::Tags::None);
let mut connection = remote.connect(remote::Direction::Fetch).await?;
if let Some(f) = self.configure_connection.as_mut() {
f(&mut connection).map_err(Error::RemoteConnection)?;
}
let refmap = connection
.ref_map_by_ref(
&mut progress,
remote::ref_map::Options {
extra_refspecs: vec![gix_refspec::parse(
"HEAD".into(),
gix_refspec::parse::Operation::Fetch,
)
.expect("valid")
.to_owned()],
..Default::default()
},
)
.await?;
let target = refmap
.remote_refs
.iter()
.find_map(|r| match r {
gix_protocol::handshake::Ref::Symbolic {
full_ref_name, target, ..
}
| gix_protocol::handshake::Ref::Unborn {
full_ref_name, target, ..
} if full_ref_name == "HEAD" => gix_ref::FullName::try_from(target)
.map_err(|err| Error::InvalidHeadRef {
head_ref_name: target.clone(),
source: err,
})
.into(),
_ => None,
})
.transpose()?;
let target = target.ok_or_else(|| Error::RefNameMissing {
wanted: "HEAD".try_into().expect("valid partial name"),
})?;
drop(connection);
remote.fetch_tags = prev_tags;
Some(target)
}
} else {
None
};
if remote.fetch_specs.is_empty() {
if let Some(target_ref) = &target_ref {
let short_name = target_ref.shorten();
remote = remote
.with_refspecs(
Some(format!("+{target_ref}:refs/remotes/{remote_name}/{short_name}").as_str()),
remote::Direction::Fetch,
)
.expect("valid refspec");
} else {
remote = remote
.with_refspecs(
Some(format!("+refs/heads/*:refs/remotes/{remote_name}/*").as_str()),
remote::Direction::Fetch,
)
.expect("valid static spec");
}
}
let mut clone_fetch_tags = None;
if let Some(f) = self.configure_remote.as_mut() {
remote = f(remote).map_err(Error::RemoteConfiguration)?;
} else {
clone_fetch_tags = remote::fetch::Tags::All.into();
}
let config = util::write_remote_to_local_config_file(&mut remote, remote_name.clone())?;
if let Some(fetch_tags) = clone_fetch_tags {
remote = remote.with_fetch_tags(fetch_tags);
}
let head_local_tracking_branch = format!("refs/remotes/{remote_name}/HEAD");
let head_refspec = gix_refspec::parse(
format!("HEAD:{head_local_tracking_branch}").as_str().into(),
gix_refspec::parse::Operation::Fetch,
)
.expect("valid")
.to_owned();
let pending_pack: remote::fetch::Prepare<'_, '_, _> = {
let mut connection = remote.connect(remote::Direction::Fetch).await?;
if let Some(f) = self.configure_connection.as_mut() {
f(&mut connection).map_err(Error::RemoteConnection)?;
}
let mut fetch_opts = {
let mut opts = self.fetch_options.clone();
if !opts.extra_refspecs.contains(&head_refspec) {
opts.extra_refspecs.push(head_refspec.clone());
}
if let Some(ref_name) = &self.ref_name {
opts.extra_refspecs.push(
gix_refspec::parse(ref_name.as_ref().as_bstr(), gix_refspec::parse::Operation::Fetch)
.expect("partial names are valid refspecs")
.to_owned(),
);
}
opts
};
match connection.prepare_fetch(&mut progress, fetch_opts.clone()).await {
Ok(prepare) => prepare,
Err(remote::fetch::prepare::Error::RefMap(remote::ref_map::Error::InitRefMap(
gix_protocol::fetch::refmap::init::Error::MappingValidation(err),
))) if err.issues.len() == 1
&& fetch_opts.extra_refspecs.contains(&head_refspec)
&& matches!(
err.issues.first(),
Some(gix_refspec::match_group::validate::Issue::Conflict {
destination_full_ref_name,
..
}) if *destination_full_ref_name == head_local_tracking_branch
) =>
{
let head_refspec_idx = fetch_opts
.extra_refspecs
.iter()
.enumerate()
.find_map(|(idx, spec)| (*spec == head_refspec).then_some(idx))
.expect("it's contained");
let connection = remote.connect(remote::Direction::Fetch).await?;
fetch_opts.extra_refspecs.remove(head_refspec_idx);
connection.prepare_fetch(&mut progress, fetch_opts).await?
}
Err(err) => return Err(err.into()),
}
};
if let Some(ref_name) = &self.ref_name {
util::find_custom_refname(pending_pack.ref_map(), ref_name)?;
}
if pending_pack.ref_map().object_hash != repo.object_hash() {
unimplemented!("configure repository to expect a different object hash as advertised by the server")
}
let reflog_message = {
let mut b = self.url.to_bstring();
b.insert_str(0, "clone: from ");
b
};
let outcome = pending_pack
.with_write_packed_refs_only(true)
.with_reflog_message(RefLogMessage::Override {
message: reflog_message.clone(),
})
.with_shallow(self.shallow.clone())
.receive(&mut progress, should_interrupt)
.await?;
util::append_config_to_repo_config(repo, config);
util::update_head(
repo,
&outcome.ref_map,
reflog_message.as_ref(),
remote_name.as_ref(),
self.ref_name.as_ref(),
)?;
Ok((self.repo.take().expect("still present"), outcome))
}
#[cfg(all(feature = "worktree-mutation", feature = "blocking-network-client"))]
pub fn fetch_then_checkout<P>(
&mut self,
progress: P,
should_interrupt: &std::sync::atomic::AtomicBool,
) -> Result<(crate::clone::PrepareCheckout, crate::remote::fetch::Outcome), Error>
where
P: crate::NestedProgress,
P::SubProgress: 'static,
{
let (repo, fetch_outcome) = self.fetch_only(progress, should_interrupt)?;
Ok((
crate::clone::PrepareCheckout {
repo: repo.into(),
ref_name: self.ref_name.clone(),
},
fetch_outcome,
))
}
}
mod util;