use std::ops::ControlFlow;
use std::sync::atomic::AtomicBool;
use super::{Endpoint, Environment, TransferCallbacks, TransferError, is_stream_failure};
use crate::fetch::{FetchLimits, FetchWorkflowError, KnownHistory, ObjectFilter};
use crate::remote::{Direction, Remote};
use crate::{IdSet, MissingObjectsError, ObjectId, Repository};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct PromisorFetch {
pub fetched: Vec<ObjectId>,
pub already_present: Vec<ObjectId>,
}
pub fn promisor_remotes(repository: &Repository) -> Vec<String> {
let config = repository.config();
let partial_clone = config
.string("extensions", None, "partialClone")
.filter(|name| !name.is_empty());
let mut names: Vec<String> = Remote::names(config)
.into_iter()
.filter(|&name| Some(name) != partial_clone)
.filter(|&name| config.boolean("remote", Some(name), "promisor") == Ok(Some(true)))
.filter_map(|name| String::from_utf8(name.to_vec()).ok())
.collect();
if let Some(name) = partial_clone.and_then(|name| String::from_utf8(name.to_vec()).ok()) {
names.push(name);
}
names
}
pub fn fetch_missing(
repository: &Repository,
remote_name: &str,
ids: &[ObjectId],
environment: &Environment,
callbacks: &mut dyn TransferCallbacks,
) -> Result<PromisorFetch, TransferError> {
if !is_promisor(repository, remote_name) {
return Err(TransferError::NotPromisor(remote_name.to_owned()));
}
let (wants, already_present) = partition(repository, ids)?;
if wants.is_empty() {
return Ok(PromisorFetch {
fetched: Vec::new(),
already_present,
});
}
let sent = receive(repository, remote_name, &wants, environment, callbacks)?;
let (fetched, missing): (Vec<_>, Vec<_>) = wants.into_iter().partition(|id| sent.contains(id));
if !missing.is_empty() {
return Err(TransferError::NotFetched(missing));
}
Ok(PromisorFetch {
fetched,
already_present,
})
}
pub fn fetch_promised(
repository: &Repository,
ids: &[ObjectId],
environment: &Environment,
callbacks: &mut dyn TransferCallbacks,
) -> Result<PromisorFetch, TransferError> {
let remotes = promisor_remotes(repository);
if remotes.is_empty() {
return Err(TransferError::NoPromisorRemote);
}
let (mut wants, already_present) = partition(repository, ids)?;
let fetched = wants.clone();
let mut first_error = None;
for remote in &remotes {
if wants.is_empty() {
break;
}
match receive(repository, remote, &wants, environment, callbacks) {
Ok(sent) => {
wants.retain(|id| !sent.contains(id));
}
Err(error) => {
first_error.get_or_insert(error);
}
}
}
if !wants.is_empty() {
return Err(first_error.unwrap_or(TransferError::NotFetched(wants)));
}
Ok(PromisorFetch {
fetched,
already_present,
})
}
pub fn fetch_needed(
repository: &Repository,
missing: &MissingObjectsError,
environment: &Environment,
callbacks: &mut dyn TransferCallbacks,
) -> Result<PromisorFetch, TransferError> {
let ids: Vec<ObjectId> = missing.ids().collect();
fetch_promised(repository, &ids, environment, callbacks)
}
fn is_promisor(repository: &Repository, remote_name: &str) -> bool {
let config = repository.config();
let name = remote_name.as_bytes();
config.string("extensions", None, "partialClone") == Some(name)
|| config.boolean("remote", Some(name), "promisor") == Ok(Some(true))
}
fn partition(
repository: &Repository,
ids: &[ObjectId],
) -> Result<(Vec<ObjectId>, Vec<ObjectId>), TransferError> {
let objects = repository
.objects_with(crate::PackLimits::trusted())
.map_err(TransferError::local)?;
let format = repository.object_format();
let mut seen = IdSet::default();
let mut wants = Vec::new();
let mut present = Vec::new();
for &id in ids {
if id.format() != format {
return Err(TransferError::local(
crate::fetch::FetchError::FormatMismatch {
remote: id.format(),
local: format,
},
));
}
if !seen.insert(id) {
continue;
}
if objects.contains(id).map_err(TransferError::local)? {
present.push(id);
} else {
wants.push(id);
}
}
Ok((wants, present))
}
fn receive(
repository: &Repository,
remote_name: &str,
wants: &[ObjectId],
environment: &Environment,
callbacks: &mut dyn TransferCallbacks,
) -> Result<IdSet, TransferError> {
let (endpoint, _) =
repository.endpoint(remote_name, Direction::Fetch, environment, &*callbacks)?;
let cancel = AtomicBool::new(false);
let limits = FetchLimits::trusted();
let mut options = crate::fetch::FetchOptions::default()
.with_limits(limits)
.with_filter(Some(ObjectFilter::BlobNone));
options.explicit_wants = true;
let select = |_: &crate::fetch::Advertisement| wants.to_vec();
let transfer = |error| TransferError::Fetch(Box::new(FetchWorkflowError::Transfer(error)));
let received = match endpoint {
Endpoint::Local(path) => crate::fetch::receive_local_observed(
path,
select,
&KnownHistory::default(),
None,
true,
limits,
crate::transport::TransportControl::new(&cancel),
|message| {
callbacks.remote_message(message);
ControlFlow::Continue(())
},
|_| {},
|_| {},
)
.map_err(transfer)?,
Endpoint::Custom(custom) => {
let mut connection = custom.connect(crate::transport::Service::UploadPack)?;
let download = crate::fetch::stream::download(
&mut **connection.get_mut(),
custom.protocol,
select,
None,
options,
&cancel,
|message| callbacks.remote_message(message),
);
let finished = connection.into_inner().finish();
let validated = download.and_then(|download| {
download.validate_with_progress(
&cancel,
|_| ControlFlow::Continue(()),
|event| callbacks.validation_progress(event),
)
});
if let (Err(cause), Err(error)) = (&validated, finished)
&& is_stream_failure(cause)
{
return Err(error.into());
}
validated.map_err(transfer)?
}
#[cfg(feature = "http")]
Endpoint::Http(http) => {
let runtime = super::runtime()?;
let control = crate::transport::TransportControl::new(&cancel);
let receive = |remote: crate::transport::http::HttpRemote,
callbacks: &mut dyn TransferCallbacks| {
runtime.block_on(crate::fetch::receive_http_with_options(
&remote,
select,
None,
options.clone(),
control,
|message| callbacks.remote_message(message),
))
};
let mut download = receive(http.remote(None)?, callbacks);
http.settle_proxy(super::fetch_status(&download));
if matches!(
&download,
Err(crate::fetch::FetchError::Http(
crate::transport::http::HttpError::Status(401)
))
) {
let session = http.credentials(callbacks)?;
download = receive(http.remote(Some(session))?, callbacks);
}
download
.and_then(|download| {
download.validate_with_progress(
&cancel,
|_| ControlFlow::Continue(()),
|event| callbacks.validation_progress(event),
)
})
.map_err(transfer)?
}
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
Endpoint::Ssh(ssh) => {
let callbacks = std::cell::RefCell::new(&mut *callbacks);
let download = super::runtime()?.block_on(crate::fetch::receive_ssh_with_progress(
&ssh,
select,
None::<std::sync::Arc<KnownHistory>>,
options,
crate::transport::TransportControl::new(&cancel),
|message| callbacks.borrow_mut().transport_message(message),
|message| callbacks.borrow_mut().remote_message(message),
));
download
.and_then(|download| {
download.validate_with_progress(
&cancel,
|_| ControlFlow::Continue(()),
|event| callbacks.borrow_mut().validation_progress(event),
)
})
.map_err(transfer)?
}
};
received
.install(repository, crate::PackLimits::trusted(), &cancel)
.map_err(|error| {
TransferError::FetchFinish(Box::new(crate::fetch::FetchFinishError {
report: Box::new(crate::fetch::FetchReport {
updates: Vec::new(),
pack_bytes: received.pack_bytes(),
objects: received.object_count(),
installed: None,
integrity: None,
shallow_published: false,
references: Vec::new(),
missing: Vec::new(),
}),
source: Box::new(crate::fetch::FetchFinishFailure::Installation(error)),
}))
})?;
Ok(received.wants().iter().copied().collect())
}