summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/remote/connection/ref_map.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix/src/remote/connection/ref_map.rs')
-rw-r--r--vendor/gix/src/remote/connection/ref_map.rs268
1 files changed, 268 insertions, 0 deletions
diff --git a/vendor/gix/src/remote/connection/ref_map.rs b/vendor/gix/src/remote/connection/ref_map.rs
new file mode 100644
index 000000000..0206e9002
--- /dev/null
+++ b/vendor/gix/src/remote/connection/ref_map.rs
@@ -0,0 +1,268 @@
+use std::collections::HashSet;
+
+use gix_features::progress::Progress;
+use gix_protocol::transport::client::Transport;
+
+use crate::{
+ bstr,
+ bstr::{BString, ByteVec},
+ remote::{connection::HandshakeWithRefs, fetch, fetch::SpecIndex, Connection, Direction},
+};
+
+/// The error returned by [`Connection::ref_map()`].
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error("Failed to configure the transport before connecting to {url:?}")]
+ GatherTransportConfig {
+ url: BString,
+ source: crate::config::transport::Error,
+ },
+ #[error("Failed to configure the transport layer")]
+ ConfigureTransport(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
+ #[error(transparent)]
+ Handshake(#[from] gix_protocol::handshake::Error),
+ #[error("The object format {format:?} as used by the remote is unsupported")]
+ UnknownObjectFormat { format: BString },
+ #[error(transparent)]
+ ListRefs(#[from] gix_protocol::ls_refs::Error),
+ #[error(transparent)]
+ Transport(#[from] gix_protocol::transport::client::Error),
+ #[error(transparent)]
+ ConfigureCredentials(#[from] crate::config::credential_helpers::Error),
+ #[error(transparent)]
+ MappingValidation(#[from] gix_refspec::match_group::validate::Error),
+}
+
+impl gix_protocol::transport::IsSpuriousError for Error {
+ fn is_spurious(&self) -> bool {
+ match self {
+ Error::Transport(err) => err.is_spurious(),
+ Error::ListRefs(err) => err.is_spurious(),
+ Error::Handshake(err) => err.is_spurious(),
+ _ => false,
+ }
+ }
+}
+
+/// For use in [`Connection::ref_map()`].
+#[derive(Debug, Clone)]
+pub struct Options {
+ /// Use a two-component prefix derived from the ref-spec's source, like `refs/heads/` to let the server pre-filter refs
+ /// with great potential for savings in traffic and local CPU time. Defaults to `true`.
+ pub prefix_from_spec_as_filter_on_remote: bool,
+ /// Parameters in the form of `(name, optional value)` to add to the handshake.
+ ///
+ /// This is useful in case of custom servers.
+ pub handshake_parameters: Vec<(String, Option<String>)>,
+ /// A list of refspecs to use as implicit refspecs which won't be saved or otherwise be part of the remote in question.
+ ///
+ /// This is useful for handling `remote.<name>.tagOpt` for example.
+ pub extra_refspecs: Vec<gix_refspec::RefSpec>,
+}
+
+impl Default for Options {
+ fn default() -> Self {
+ Options {
+ prefix_from_spec_as_filter_on_remote: true,
+ handshake_parameters: Vec::new(),
+ extra_refspecs: Vec::new(),
+ }
+ }
+}
+
+impl<'remote, 'repo, T, P> Connection<'remote, 'repo, T, P>
+where
+ T: Transport,
+ P: Progress,
+{
+ /// List all references on the remote that have been filtered through our remote's [`refspecs`][crate::Remote::refspecs()]
+ /// for _fetching_.
+ ///
+ /// This comes in the form of all matching tips on the remote and the object they point to, along with
+ /// with the local tracking branch of these tips (if available).
+ ///
+ /// Note that this doesn't fetch the objects mentioned in the tips nor does it make any change to underlying repository.
+ ///
+ /// # Consumption
+ ///
+ /// Due to management of the transport, it's cleanest to only use it for a single interaction. Thus it's consumed along with
+ /// the connection.
+ ///
+ /// ### Configuration
+ ///
+ /// - `gitoxide.userAgent` is read to obtain the application user agent for git servers and for HTTP servers as well.
+ #[allow(clippy::result_large_err)]
+ #[gix_protocol::maybe_async::maybe_async]
+ pub async fn ref_map(mut self, options: Options) -> Result<fetch::RefMap, Error> {
+ let res = self.ref_map_inner(options).await;
+ gix_protocol::indicate_end_of_interaction(&mut self.transport)
+ .await
+ .ok();
+ res
+ }
+
+ #[allow(clippy::result_large_err)]
+ #[gix_protocol::maybe_async::maybe_async]
+ pub(crate) async fn ref_map_inner(
+ &mut self,
+ Options {
+ prefix_from_spec_as_filter_on_remote,
+ handshake_parameters,
+ mut extra_refspecs,
+ }: Options,
+ ) -> Result<fetch::RefMap, Error> {
+ let null = gix_hash::ObjectId::null(gix_hash::Kind::Sha1); // OK to hardcode Sha1, it's not supposed to match, ever.
+
+ if let Some(tag_spec) = self.remote.fetch_tags.to_refspec().map(|spec| spec.to_owned()) {
+ if !extra_refspecs.contains(&tag_spec) {
+ extra_refspecs.push(tag_spec);
+ }
+ };
+ let specs = {
+ let mut s = self.remote.fetch_specs.clone();
+ s.extend(extra_refspecs.clone());
+ s
+ };
+ let remote = self
+ .fetch_refs(prefix_from_spec_as_filter_on_remote, handshake_parameters, &specs)
+ .await?;
+ let num_explicit_specs = self.remote.fetch_specs.len();
+ let group = gix_refspec::MatchGroup::from_fetch_specs(specs.iter().map(|s| s.to_ref()));
+ let (res, fixes) = group
+ .match_remotes(remote.refs.iter().map(|r| {
+ let (full_ref_name, target, object) = r.unpack();
+ gix_refspec::match_group::Item {
+ full_ref_name,
+ target: target.unwrap_or(&null),
+ object,
+ }
+ }))
+ .validated()?;
+ let mappings = res.mappings;
+ let mappings = mappings
+ .into_iter()
+ .map(|m| fetch::Mapping {
+ remote: m
+ .item_index
+ .map(|idx| fetch::Source::Ref(remote.refs[idx].clone()))
+ .unwrap_or_else(|| {
+ fetch::Source::ObjectId(match m.lhs {
+ gix_refspec::match_group::SourceRef::ObjectId(id) => id,
+ _ => unreachable!("no item index implies having an object id"),
+ })
+ }),
+ local: m.rhs.map(|c| c.into_owned()),
+ spec_index: if m.spec_index < num_explicit_specs {
+ SpecIndex::ExplicitInRemote(m.spec_index)
+ } else {
+ SpecIndex::Implicit(m.spec_index - num_explicit_specs)
+ },
+ })
+ .collect();
+
+ let object_hash = extract_object_format(self.remote.repo, &remote.outcome)?;
+ Ok(fetch::RefMap {
+ mappings,
+ extra_refspecs,
+ fixes,
+ remote_refs: remote.refs,
+ handshake: remote.outcome,
+ object_hash,
+ })
+ }
+
+ #[allow(clippy::result_large_err)]
+ #[gix_protocol::maybe_async::maybe_async]
+ async fn fetch_refs(
+ &mut self,
+ filter_by_prefix: bool,
+ extra_parameters: Vec<(String, Option<String>)>,
+ refspecs: &[gix_refspec::RefSpec],
+ ) -> Result<HandshakeWithRefs, Error> {
+ let mut credentials_storage;
+ let url = self.transport.to_url();
+ let authenticate = match self.authenticate.as_mut() {
+ Some(f) => f,
+ None => {
+ let url = self
+ .remote
+ .url(Direction::Fetch)
+ .map(ToOwned::to_owned)
+ .unwrap_or_else(|| gix_url::parse(url.as_ref()).expect("valid URL to be provided by transport"));
+ credentials_storage = self.configured_credentials(url)?;
+ &mut credentials_storage
+ }
+ };
+
+ if self.transport_options.is_none() {
+ self.transport_options = self
+ .remote
+ .repo
+ .transport_options(url.as_ref(), self.remote.name().map(|n| n.as_bstr()))
+ .map_err(|err| Error::GatherTransportConfig {
+ source: err,
+ url: url.into_owned(),
+ })?;
+ }
+ if let Some(config) = self.transport_options.as_ref() {
+ self.transport.configure(&**config)?;
+ }
+ let mut outcome =
+ gix_protocol::fetch::handshake(&mut self.transport, authenticate, extra_parameters, &mut self.progress)
+ .await?;
+ let refs = match outcome.refs.take() {
+ Some(refs) => refs,
+ None => {
+ let agent_feature = self.remote.repo.config.user_agent_tuple();
+ gix_protocol::ls_refs(
+ &mut self.transport,
+ &outcome.capabilities,
+ move |_capabilities, arguments, features| {
+ features.push(agent_feature);
+ if filter_by_prefix {
+ let mut seen = HashSet::new();
+ for spec in refspecs {
+ let spec = spec.to_ref();
+ if seen.insert(spec.instruction()) {
+ let mut prefixes = Vec::with_capacity(1);
+ spec.expand_prefixes(&mut prefixes);
+ for mut prefix in prefixes {
+ prefix.insert_str(0, "ref-prefix ");
+ arguments.push(prefix);
+ }
+ }
+ }
+ }
+ Ok(gix_protocol::ls_refs::Action::Continue)
+ },
+ &mut self.progress,
+ )
+ .await?
+ }
+ };
+ Ok(HandshakeWithRefs { outcome, refs })
+ }
+}
+
+/// Assume sha1 if server says nothing, otherwise configure anything beyond sha1 in the local repo configuration
+#[allow(clippy::result_large_err)]
+fn extract_object_format(
+ _repo: &crate::Repository,
+ outcome: &gix_protocol::handshake::Outcome,
+) -> Result<gix_hash::Kind, Error> {
+ use bstr::ByteSlice;
+ let object_hash =
+ if let Some(object_format) = outcome.capabilities.capability("object-format").and_then(|c| c.value()) {
+ let object_format = object_format.to_str().map_err(|_| Error::UnknownObjectFormat {
+ format: object_format.into(),
+ })?;
+ match object_format {
+ "sha1" => gix_hash::Kind::Sha1,
+ unknown => return Err(Error::UnknownObjectFormat { format: unknown.into() }),
+ }
+ } else {
+ gix_hash::Kind::Sha1
+ };
+ Ok(object_hash)
+}