summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/repository
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix/src/repository
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix/src/repository')
-rw-r--r--vendor/gix/src/repository/cache.rs30
-rw-r--r--vendor/gix/src/repository/config/mod.rs191
-rw-r--r--vendor/gix/src/repository/config/transport.rs425
-rw-r--r--vendor/gix/src/repository/identity.rs175
-rw-r--r--vendor/gix/src/repository/impls.rs73
-rw-r--r--vendor/gix/src/repository/init.rs55
-rw-r--r--vendor/gix/src/repository/location.rs86
-rw-r--r--vendor/gix/src/repository/mod.rs36
-rw-r--r--vendor/gix/src/repository/object.rs214
-rw-r--r--vendor/gix/src/repository/permissions.rs168
-rw-r--r--vendor/gix/src/repository/reference.rs243
-rw-r--r--vendor/gix/src/repository/remote.rs199
-rw-r--r--vendor/gix/src/repository/revision.rs42
-rw-r--r--vendor/gix/src/repository/snapshots.rs109
-rw-r--r--vendor/gix/src/repository/state.rs44
-rw-r--r--vendor/gix/src/repository/thread_safe.rs66
-rw-r--r--vendor/gix/src/repository/worktree.rs119
17 files changed, 2275 insertions, 0 deletions
diff --git a/vendor/gix/src/repository/cache.rs b/vendor/gix/src/repository/cache.rs
new file mode 100644
index 000000000..7dcd844e6
--- /dev/null
+++ b/vendor/gix/src/repository/cache.rs
@@ -0,0 +1,30 @@
+/// Configure how caches are used to speed up various git repository operations
+impl crate::Repository {
+ /// Sets the amount of space used at most for caching most recently accessed fully decoded objects, to `Some(bytes)`,
+ /// or `None` to deactivate it entirely.
+ ///
+ /// Note that it is unset by default but can be enabled once there is time for performance optimization.
+ /// Well-chosen cache sizes can improve performance particularly if objects are accessed multiple times in a row.
+ /// The cache is configured to grow gradually.
+ ///
+ /// Note that a cache on application level should be considered as well as the best object access is not doing one.
+ pub fn object_cache_size(&mut self, bytes: impl Into<Option<usize>>) {
+ let bytes = bytes.into();
+ match bytes {
+ Some(bytes) if bytes == 0 => self.objects.unset_object_cache(),
+ Some(bytes) => self
+ .objects
+ .set_object_cache(move || Box::new(crate::object::cache::MemoryCappedHashmap::new(bytes))),
+ None => self.objects.unset_object_cache(),
+ }
+ }
+
+ /// Set an object cache of size `bytes` if none is set.
+ ///
+ /// Use this method to avoid overwriting any existing value while assuring better performance in case no value is set.
+ pub fn object_cache_size_if_unset(&mut self, bytes: usize) {
+ if !self.objects.has_object_cache() {
+ self.object_cache_size(bytes)
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/config/mod.rs b/vendor/gix/src/repository/config/mod.rs
new file mode 100644
index 000000000..92b2618cc
--- /dev/null
+++ b/vendor/gix/src/repository/config/mod.rs
@@ -0,0 +1,191 @@
+use std::collections::BTreeSet;
+
+use crate::{bstr::ByteSlice, config};
+
+/// General Configuration
+impl crate::Repository {
+ /// Return a snapshot of the configuration as seen upon opening the repository.
+ pub fn config_snapshot(&self) -> config::Snapshot<'_> {
+ config::Snapshot { repo: self }
+ }
+
+ /// Return a mutable snapshot of the configuration as seen upon opening the repository, starting a transaction.
+ /// When the returned instance is dropped, it is applied in full, even if the reason for the drop is an error.
+ ///
+ /// Note that changes to the configuration are in-memory only and are observed only the this instance
+ /// of the [`Repository`][crate::Repository].
+ pub fn config_snapshot_mut(&mut self) -> config::SnapshotMut<'_> {
+ let config = self.config.resolved.as_ref().clone();
+ config::SnapshotMut {
+ repo: Some(self),
+ config,
+ }
+ }
+
+ /// The options used to open the repository.
+ pub fn open_options(&self) -> &crate::open::Options {
+ &self.options
+ }
+
+ /// Obtain options for use when connecting via `ssh`.
+ #[cfg(feature = "blocking-network-client")]
+ pub fn ssh_connect_options(
+ &self,
+ ) -> Result<gix_protocol::transport::client::ssh::connect::Options, config::ssh_connect_options::Error> {
+ use crate::config::{
+ cache::util::ApplyLeniency,
+ tree::{gitoxide, Core, Ssh},
+ };
+
+ let config = &self.config.resolved;
+ let mut trusted = self.filter_config_section();
+ let mut fallback_active = false;
+ let ssh_command = config
+ .string_filter("core", None, Core::SSH_COMMAND.name, &mut trusted)
+ .or_else(|| {
+ fallback_active = true;
+ config.string_filter(
+ "gitoxide",
+ Some("ssh".into()),
+ gitoxide::Ssh::COMMAND_WITHOUT_SHELL_FALLBACK.name,
+ &mut trusted,
+ )
+ })
+ .map(|cmd| gix_path::from_bstr(cmd).into_owned().into());
+ let opts = gix_protocol::transport::client::ssh::connect::Options {
+ disallow_shell: fallback_active,
+ command: ssh_command,
+ kind: config
+ .string_filter_by_key("ssh.variant", &mut trusted)
+ .and_then(|variant| Ssh::VARIANT.try_into_variant(variant).transpose())
+ .transpose()
+ .with_leniency(self.options.lenient_config)?,
+ };
+ Ok(opts)
+ }
+
+ /// The kind of object hash the repository is configured to use.
+ pub fn object_hash(&self) -> gix_hash::Kind {
+ self.config.object_hash
+ }
+}
+
+#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
+mod transport;
+
+mod remote {
+ use std::{borrow::Cow, collections::BTreeSet};
+
+ use crate::{bstr::ByteSlice, remote};
+
+ impl crate::Repository {
+ /// Returns a sorted list unique of symbolic names of remotes that
+ /// we deem [trustworthy][crate::open::Options::filter_config_section()].
+ // TODO: Use `remote::Name` here
+ pub fn remote_names(&self) -> BTreeSet<&str> {
+ self.subsection_names_of("remote")
+ }
+
+ /// Obtain the branch-independent name for a remote for use in the given `direction`, or `None` if it could not be determined.
+ ///
+ /// For _fetching_, use the only configured remote, or default to `origin` if it exists.
+ /// For _pushing_, use the `remote.pushDefault` trusted configuration key, or fall back to the rules for _fetching_.
+ ///
+ /// # Notes
+ ///
+ /// It's up to the caller to determine what to do if the current `head` is unborn or detached.
+ // TODO: use remote::Name here
+ pub fn remote_default_name(&self, direction: remote::Direction) -> Option<Cow<'_, str>> {
+ let name = (direction == remote::Direction::Push)
+ .then(|| {
+ self.config
+ .resolved
+ .string_filter("remote", None, "pushDefault", &mut self.filter_config_section())
+ .and_then(|s| match s {
+ Cow::Borrowed(s) => s.to_str().ok().map(Cow::Borrowed),
+ Cow::Owned(s) => s.to_str().ok().map(|s| Cow::Owned(s.into())),
+ })
+ })
+ .flatten();
+ name.or_else(|| {
+ let names = self.remote_names();
+ match names.len() {
+ 0 => None,
+ 1 => names.iter().next().copied().map(Cow::Borrowed),
+ _more_than_one => names.get("origin").copied().map(Cow::Borrowed),
+ }
+ })
+ }
+ }
+}
+
+mod branch {
+ use std::{borrow::Cow, collections::BTreeSet, convert::TryInto};
+
+ use gix_ref::FullNameRef;
+ use gix_validate::reference::name::Error as ValidateNameError;
+
+ use crate::bstr::BStr;
+
+ impl crate::Repository {
+ /// Return a set of unique short branch names for which custom configuration exists in the configuration,
+ /// if we deem them [trustworthy][crate::open::Options::filter_config_section()].
+ pub fn branch_names(&self) -> BTreeSet<&str> {
+ self.subsection_names_of("branch")
+ }
+
+ /// Returns the validated reference on the remote associated with the given `short_branch_name`,
+ /// always `main` instead of `refs/heads/main`.
+ ///
+ /// The returned reference is the one we track on the remote side for merging and pushing.
+ /// Returns `None` if the remote reference was not found.
+ /// May return an error if the reference is invalid.
+ pub fn branch_remote_ref<'a>(
+ &self,
+ short_branch_name: impl Into<&'a BStr>,
+ ) -> Option<Result<Cow<'_, FullNameRef>, ValidateNameError>> {
+ self.config
+ .resolved
+ .string("branch", Some(short_branch_name.into()), "merge")
+ .map(crate::config::tree::branch::Merge::try_into_fullrefname)
+ }
+
+ /// Returns the unvalidated name of the remote associated with the given `short_branch_name`,
+ /// typically `main` instead of `refs/heads/main`.
+ /// In some cases, the returned name will be an URL.
+ /// Returns `None` if the remote was not found or if the name contained illformed UTF-8.
+ ///
+ /// See also [Reference::remote_name()][crate::Reference::remote_name()] for a more typesafe version
+ /// to be used when a `Reference` is available.
+ pub fn branch_remote_name<'a>(
+ &self,
+ short_branch_name: impl Into<&'a BStr>,
+ ) -> Option<crate::remote::Name<'_>> {
+ self.config
+ .resolved
+ .string("branch", Some(short_branch_name.into()), "remote")
+ .and_then(|name| name.try_into().ok())
+ }
+ }
+}
+
+impl crate::Repository {
+ pub(crate) fn filter_config_section(&self) -> fn(&gix_config::file::Metadata) -> bool {
+ self.options
+ .filter_config_section
+ .unwrap_or(config::section::is_trusted)
+ }
+
+ fn subsection_names_of<'a>(&'a self, header_name: &'a str) -> BTreeSet<&'a str> {
+ self.config
+ .resolved
+ .sections_by_name(header_name)
+ .map(|it| {
+ let filter = self.filter_config_section();
+ it.filter(move |s| filter(s.meta()))
+ .filter_map(|section| section.header().subsection_name().and_then(|b| b.to_str().ok()))
+ .collect()
+ })
+ .unwrap_or_default()
+ }
+}
diff --git a/vendor/gix/src/repository/config/transport.rs b/vendor/gix/src/repository/config/transport.rs
new file mode 100644
index 000000000..dcfbc0bf6
--- /dev/null
+++ b/vendor/gix/src/repository/config/transport.rs
@@ -0,0 +1,425 @@
+#![allow(clippy::result_large_err)]
+use std::any::Any;
+
+use crate::bstr::BStr;
+
+impl crate::Repository {
+ /// Produce configuration suitable for `url`, as differentiated by its protocol/scheme, to be passed to a transport instance via
+ /// [configure()][gix_transport::client::TransportWithoutIO::configure()] (via `&**config` to pass the contained `Any` and not the `Box`).
+ /// `None` is returned if there is no known configuration. If `remote_name` is not `None`, the remote's name may contribute to
+ /// configuration overrides, typically for the HTTP transport.
+ ///
+ /// Note that the caller may cast the instance themselves to modify it before passing it on.
+ ///
+ /// For transports that support proxy authentication, the
+ /// [default authentication method](crate::config::Snapshot::credential_helpers()) will be used with the url of the proxy
+ /// if it contains a user name.
+ #[cfg_attr(
+ not(any(
+ feature = "blocking-http-transport-reqwest",
+ feature = "blocking-http-transport-curl"
+ )),
+ allow(unused_variables)
+ )]
+ pub fn transport_options<'a>(
+ &self,
+ url: impl Into<&'a BStr>,
+ remote_name: Option<&BStr>,
+ ) -> Result<Option<Box<dyn Any>>, crate::config::transport::Error> {
+ let url = gix_url::parse(url.into())?;
+ use gix_url::Scheme::*;
+
+ match &url.scheme {
+ Http | Https => {
+ #[cfg(not(any(
+ feature = "blocking-http-transport-reqwest",
+ feature = "blocking-http-transport-curl"
+ )))]
+ {
+ Ok(None)
+ }
+ #[cfg(any(
+ feature = "blocking-http-transport-reqwest",
+ feature = "blocking-http-transport-curl"
+ ))]
+ {
+ use std::{
+ borrow::Cow,
+ sync::{Arc, Mutex},
+ };
+
+ use gix_transport::client::{
+ http,
+ http::options::{ProxyAuthMethod, SslVersion, SslVersionRangeInclusive},
+ };
+
+ use crate::{
+ config,
+ config::{
+ cache::util::ApplyLeniency,
+ tree::{gitoxide, Key, Remote},
+ },
+ };
+ fn try_cow_to_string(
+ v: Cow<'_, BStr>,
+ lenient: bool,
+ key_str: impl Into<Cow<'static, BStr>>,
+ key: &'static config::tree::keys::String,
+ ) -> Result<Option<String>, config::transport::Error> {
+ key.try_into_string(v)
+ .map_err(|err| config::transport::Error::IllformedUtf8 {
+ source: err,
+ key: key_str.into(),
+ })
+ .map(Some)
+ .with_leniency(lenient)
+ }
+
+ fn cow_bstr(v: &str) -> Cow<'_, BStr> {
+ Cow::Borrowed(v.into())
+ }
+
+ fn proxy_auth_method(
+ value_and_key: Option<(
+ Cow<'_, BStr>,
+ Cow<'static, BStr>,
+ &'static config::tree::http::ProxyAuthMethod,
+ )>,
+ ) -> Result<ProxyAuthMethod, config::transport::Error> {
+ let value = value_and_key
+ .map(|(method, key, key_type)| {
+ key_type.try_into_proxy_auth_method(method).map_err(|err| {
+ config::transport::http::Error::InvalidProxyAuthMethod { source: err, key }
+ })
+ })
+ .transpose()?
+ .unwrap_or_default();
+ Ok(value)
+ }
+
+ fn ssl_version(
+ config: &gix_config::File<'static>,
+ key_str: &'static str,
+ key: &'static config::tree::http::SslVersion,
+ mut filter: fn(&gix_config::file::Metadata) -> bool,
+ lenient: bool,
+ ) -> Result<Option<SslVersion>, config::transport::Error> {
+ debug_assert_eq!(
+ key_str,
+ key.logical_name(),
+ "BUG: hardcoded and generated key names must match"
+ );
+ config
+ .string_filter_by_key(key_str, &mut filter)
+ .filter(|v| !v.is_empty())
+ .map(|v| {
+ key.try_into_ssl_version(v)
+ .map_err(crate::config::transport::http::Error::from)
+ })
+ .transpose()
+ .with_leniency(lenient)
+ .map_err(Into::into)
+ }
+
+ fn proxy(
+ value: Option<(Cow<'_, BStr>, Cow<'static, BStr>, &'static config::tree::keys::String)>,
+ lenient: bool,
+ ) -> Result<Option<String>, config::transport::Error> {
+ Ok(value
+ .and_then(|(v, k, key)| try_cow_to_string(v, lenient, k.clone(), key).transpose())
+ .transpose()?
+ .map(|mut proxy| {
+ if !proxy.trim().is_empty() && !proxy.contains("://") {
+ proxy.insert_str(0, "http://");
+ proxy
+ } else {
+ proxy
+ }
+ }))
+ }
+
+ let mut opts = http::Options::default();
+ let config = &self.config.resolved;
+ let mut trusted_only = self.filter_config_section();
+ let lenient = self.config.lenient_config;
+ opts.extra_headers = {
+ let key = "http.extraHeader";
+ debug_assert_eq!(key, &config::tree::Http::EXTRA_HEADER.logical_name());
+ config
+ .strings_filter_by_key(key, &mut trusted_only)
+ .map(|values| config::tree::Http::EXTRA_HEADER.try_into_extra_header(values))
+ .transpose()
+ .map_err(|err| config::transport::Error::IllformedUtf8 {
+ source: err,
+ key: Cow::Borrowed(key.into()),
+ })?
+ .unwrap_or_default()
+ };
+
+ opts.follow_redirects = {
+ let key = "http.followRedirects";
+
+ config::tree::Http::FOLLOW_REDIRECTS
+ .try_into_follow_redirects(
+ config.string_filter_by_key(key, &mut trusted_only).unwrap_or_default(),
+ || {
+ config
+ .boolean_filter_by_key(key, &mut trusted_only)
+ .transpose()
+ .with_leniency(lenient)
+ },
+ )
+ .map_err(config::transport::http::Error::InvalidFollowRedirects)?
+ };
+
+ opts.low_speed_time_seconds = config
+ .integer_filter_by_key("http.lowSpeedTime", &mut trusted_only)
+ .map(|value| config::tree::Http::LOW_SPEED_TIME.try_into_u64(value))
+ .transpose()
+ .with_leniency(lenient)
+ .map_err(config::transport::http::Error::from)?
+ .unwrap_or_default();
+ opts.low_speed_limit_bytes_per_second = config
+ .integer_filter_by_key("http.lowSpeedLimit", &mut trusted_only)
+ .map(|value| config::tree::Http::LOW_SPEED_LIMIT.try_into_u32(value))
+ .transpose()
+ .with_leniency(lenient)
+ .map_err(config::transport::http::Error::from)?
+ .unwrap_or_default();
+ opts.proxy = proxy(
+ remote_name
+ .and_then(|name| {
+ config
+ .string_filter("remote", Some(name), Remote::PROXY.name, &mut trusted_only)
+ .map(|v| (v, Cow::Owned(format!("remote.{name}.proxy").into()), &Remote::PROXY))
+ })
+ .or_else(|| {
+ let key = "http.proxy";
+ debug_assert_eq!(key, config::tree::Http::PROXY.logical_name());
+ let http_proxy = config
+ .string_filter_by_key(key, &mut trusted_only)
+ .map(|v| (v, cow_bstr(key), &config::tree::Http::PROXY))
+ .or_else(|| {
+ let key = "gitoxide.http.proxy";
+ debug_assert_eq!(key, gitoxide::Http::PROXY.logical_name());
+ config
+ .string_filter_by_key(key, &mut trusted_only)
+ .map(|v| (v, cow_bstr(key), &gitoxide::Http::PROXY))
+ });
+ if url.scheme == Https {
+ http_proxy.or_else(|| {
+ let key = "gitoxide.https.proxy";
+ debug_assert_eq!(key, gitoxide::Https::PROXY.logical_name());
+ config
+ .string_filter_by_key(key, &mut trusted_only)
+ .map(|v| (v, cow_bstr(key), &gitoxide::Https::PROXY))
+ })
+ } else {
+ http_proxy
+ }
+ })
+ .or_else(|| {
+ let key = "gitoxide.http.allProxy";
+ debug_assert_eq!(key, gitoxide::Http::ALL_PROXY.logical_name());
+ config
+ .string_filter_by_key(key, &mut trusted_only)
+ .map(|v| (v, cow_bstr(key), &gitoxide::Http::ALL_PROXY))
+ }),
+ lenient,
+ )?;
+ {
+ let key = "gitoxide.http.noProxy";
+ debug_assert_eq!(key, gitoxide::Http::NO_PROXY.logical_name());
+ opts.no_proxy = config
+ .string_filter_by_key(key, &mut trusted_only)
+ .and_then(|v| {
+ try_cow_to_string(v, lenient, Cow::Borrowed(key.into()), &gitoxide::Http::NO_PROXY)
+ .transpose()
+ })
+ .transpose()?;
+ }
+ opts.proxy_auth_method = proxy_auth_method({
+ let key = "gitoxide.http.proxyAuthMethod";
+ debug_assert_eq!(key, gitoxide::Http::PROXY_AUTH_METHOD.logical_name());
+ config
+ .string_filter_by_key(key, &mut trusted_only)
+ .map(|v| (v, Cow::Borrowed(key.into()), &gitoxide::Http::PROXY_AUTH_METHOD))
+ .or_else(|| {
+ remote_name
+ .and_then(|name| {
+ config
+ .string_filter("remote", Some(name), "proxyAuthMethod", &mut trusted_only)
+ .map(|v| {
+ (
+ v,
+ Cow::Owned(format!("remote.{name}.proxyAuthMethod").into()),
+ &Remote::PROXY_AUTH_METHOD,
+ )
+ })
+ })
+ .or_else(|| {
+ let key = "http.proxyAuthMethod";
+ debug_assert_eq!(key, config::tree::Http::PROXY_AUTH_METHOD.logical_name());
+ config.string_filter_by_key(key, &mut trusted_only).map(|v| {
+ (v, Cow::Borrowed(key.into()), &config::tree::Http::PROXY_AUTH_METHOD)
+ })
+ })
+ })
+ })?;
+ opts.proxy_authenticate = opts
+ .proxy
+ .as_deref()
+ .filter(|url| !url.is_empty())
+ .map(|url| gix_url::parse(url.into()))
+ .transpose()?
+ .filter(|url| url.user().is_some())
+ .map(|url| -> Result<_, config::transport::http::Error> {
+ let (mut cascade, action_with_normalized_url, prompt_opts) =
+ self.config_snapshot().credential_helpers(url)?;
+ Ok((
+ action_with_normalized_url,
+ Arc::new(Mutex::new(move |action| cascade.invoke(action, prompt_opts.clone())))
+ as Arc<Mutex<http::options::AuthenticateFn>>,
+ ))
+ })
+ .transpose()?;
+ opts.connect_timeout = {
+ let key = "gitoxide.http.connectTimeout";
+ config
+ .integer_filter_by_key(key, &mut trusted_only)
+ .map(|v| {
+ debug_assert_eq!(key, gitoxide::Http::CONNECT_TIMEOUT.logical_name());
+ gitoxide::Http::CONNECT_TIMEOUT
+ .try_into_duration(v)
+ .map_err(crate::config::transport::http::Error::from)
+ })
+ .transpose()
+ .with_leniency(lenient)?
+ };
+ {
+ let key = "http.userAgent";
+ opts.user_agent = config
+ .string_filter_by_key(key, &mut trusted_only)
+ .and_then(|v| {
+ try_cow_to_string(
+ v,
+ lenient,
+ Cow::Borrowed(key.into()),
+ &config::tree::Http::USER_AGENT,
+ )
+ .transpose()
+ })
+ .transpose()?
+ .or_else(|| Some(crate::env::agent().into()));
+ }
+
+ {
+ let key = "http.version";
+ opts.http_version = config
+ .string_filter_by_key(key, &mut trusted_only)
+ .map(|v| {
+ config::tree::Http::VERSION
+ .try_into_http_version(v)
+ .map_err(config::transport::http::Error::InvalidHttpVersion)
+ })
+ .transpose()?;
+ }
+
+ {
+ opts.verbose = config
+ .boolean_filter(
+ "gitoxide",
+ Some("http".into()),
+ gitoxide::Http::VERBOSE.name,
+ &mut trusted_only,
+ )
+ .and_then(Result::ok)
+ .unwrap_or_default();
+ }
+
+ let may_use_cainfo = {
+ let key = "http.schannelUseSSLCAInfo";
+ config
+ .boolean_filter_by_key(key, &mut trusted_only)
+ .map(|value| config::tree::Http::SCHANNEL_USE_SSL_CA_INFO.enrich_error(value))
+ .transpose()
+ .with_leniency(lenient)
+ .map_err(config::transport::http::Error::from)?
+ .unwrap_or(true)
+ };
+
+ if may_use_cainfo {
+ let key = "http.sslCAInfo";
+ debug_assert_eq!(key, config::tree::Http::SSL_CA_INFO.logical_name());
+ opts.ssl_ca_info = config
+ .path_filter_by_key(key, &mut trusted_only)
+ .map(|p| {
+ use crate::config::cache::interpolate_context;
+ p.interpolate(interpolate_context(
+ self.install_dir().ok().as_deref(),
+ self.config.home_dir().as_deref(),
+ ))
+ .map(|cow| cow.into_owned())
+ })
+ .transpose()
+ .with_leniency(lenient)
+ .map_err(|err| config::transport::Error::InterpolatePath { source: err, key })?;
+ }
+
+ {
+ opts.ssl_version = ssl_version(
+ config,
+ "http.sslVersion",
+ &config::tree::Http::SSL_VERSION,
+ trusted_only,
+ lenient,
+ )?
+ .map(|v| SslVersionRangeInclusive { min: v, max: v });
+ let min_max = ssl_version(
+ config,
+ "gitoxide.http.sslVersionMin",
+ &gitoxide::Http::SSL_VERSION_MIN,
+ trusted_only,
+ lenient,
+ )
+ .and_then(|min| {
+ ssl_version(
+ config,
+ "gitoxide.http.sslVersionMax",
+ &gitoxide::Http::SSL_VERSION_MAX,
+ trusted_only,
+ lenient,
+ )
+ .map(|max| min.and_then(|min| max.map(|max| (min, max))))
+ })?;
+ if let Some((min, max)) = min_max {
+ let v = opts.ssl_version.get_or_insert(SslVersionRangeInclusive {
+ min: SslVersion::TlsV1_3,
+ max: SslVersion::TlsV1_3,
+ });
+ v.min = min;
+ v.max = max;
+ }
+ }
+
+ #[cfg(feature = "blocking-http-transport-curl")]
+ {
+ let key = "http.schannelCheckRevoke";
+ let schannel_check_revoke = config
+ .boolean_filter_by_key(key, &mut trusted_only)
+ .map(|value| config::tree::Http::SCHANNEL_CHECK_REVOKE.enrich_error(value))
+ .transpose()
+ .with_leniency(lenient)
+ .map_err(config::transport::http::Error::from)?;
+ let backend = gix_protocol::transport::client::http::curl::Options { schannel_check_revoke };
+ opts.backend =
+ Some(Arc::new(Mutex::new(backend)) as Arc<Mutex<dyn Any + Send + Sync + 'static>>);
+ }
+
+ Ok(Some(Box::new(opts)))
+ }
+ }
+ File | Git | Ssh | Ext(_) => Ok(None),
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/identity.rs b/vendor/gix/src/repository/identity.rs
new file mode 100644
index 000000000..61a4b4a98
--- /dev/null
+++ b/vendor/gix/src/repository/identity.rs
@@ -0,0 +1,175 @@
+use std::time::SystemTime;
+
+use crate::{
+ bstr::BString,
+ config,
+ config::tree::{gitoxide, keys, Author, Committer, Key, User},
+};
+
+/// Identity handling.
+///
+/// # Deviation
+///
+/// There is no notion of a default user like in git, and instead failing to provide a user
+/// is fatal. That way, we enforce correctness and force application developers to take care
+/// of this issue which can be done in various ways, for instance by setting
+/// `gitoxide.committer.nameFallback` and similar.
+impl crate::Repository {
+ /// Return the committer as configured by this repository, which is determined by…
+ ///
+ /// * …the git configuration `committer.name|email`…
+ /// * …the `GIT_COMMITTER_(NAME|EMAIL|DATE)` environment variables…
+ /// * …the configuration for `user.name|email` as fallback…
+ ///
+ /// …and in that order, or `None` if no committer name or email was configured, or `Some(Err(…))`
+ /// if the committer date could not be parsed.
+ ///
+ /// # Note
+ ///
+ /// The values are cached when the repository is instantiated.
+ pub fn committer(&self) -> Option<Result<gix_actor::SignatureRef<'_>, config::time::Error>> {
+ let p = self.config.personas();
+
+ Ok(gix_actor::SignatureRef {
+ name: p.committer.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?,
+ email: p
+ .committer
+ .email
+ .as_ref()
+ .or(p.user.email.as_ref())
+ .map(|v| v.as_ref())?,
+ time: match extract_time_or_default(p.committer.time.as_ref(), &gitoxide::Commit::COMMITTER_DATE) {
+ Ok(t) => t,
+ Err(err) => return Some(Err(err)),
+ },
+ })
+ .into()
+ }
+
+ /// Return the author as configured by this repository, which is determined by…
+ ///
+ /// * …the git configuration `author.name|email`…
+ /// * …the `GIT_AUTHOR_(NAME|EMAIL|DATE)` environment variables…
+ /// * …the configuration for `user.name|email` as fallback…
+ ///
+ /// …and in that order, or `None` if there was nothing configured.
+ ///
+ /// # Note
+ ///
+ /// The values are cached when the repository is instantiated.
+ pub fn author(&self) -> Option<Result<gix_actor::SignatureRef<'_>, config::time::Error>> {
+ let p = self.config.personas();
+
+ Ok(gix_actor::SignatureRef {
+ name: p.author.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?,
+ email: p.author.email.as_ref().or(p.user.email.as_ref()).map(|v| v.as_ref())?,
+ time: match extract_time_or_default(p.author.time.as_ref(), &gitoxide::Commit::AUTHOR_DATE) {
+ Ok(t) => t,
+ Err(err) => return Some(Err(err)),
+ },
+ })
+ .into()
+ }
+}
+
+fn extract_time_or_default(
+ time: Option<&Result<gix_actor::Time, gix_date::parse::Error>>,
+ config_key: &'static keys::Time,
+) -> Result<gix_actor::Time, config::time::Error> {
+ match time {
+ Some(Ok(t)) => Ok(*t),
+ None => Ok(gix_date::Time::now_local_or_utc()),
+ Some(Err(err)) => Err(config::time::Error::from(config_key).with_source(err.clone())),
+ }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct Entity {
+ pub name: Option<BString>,
+ pub email: Option<BString>,
+ /// A time parsed from an environment variable, handling potential errors is delayed.
+ pub time: Option<Result<gix_actor::Time, gix_date::parse::Error>>,
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct Personas {
+ user: Entity,
+ committer: Entity,
+ author: Entity,
+}
+
+impl Personas {
+ pub fn from_config_and_env(config: &gix_config::File<'_>) -> Self {
+ fn entity_in_section(
+ config: &gix_config::File<'_>,
+ name_key: &keys::Any,
+ email_key: &keys::Any,
+ fallback: Option<(&keys::Any, &keys::Any)>,
+ ) -> (Option<BString>, Option<BString>) {
+ let fallback = fallback.and_then(|(name_key, email_key)| {
+ debug_assert_eq!(name_key.section.name(), email_key.section.name());
+ config
+ .section("gitoxide", Some(name_key.section.name().into()))
+ .ok()
+ .map(|section| (section, name_key, email_key))
+ });
+ (
+ config
+ .string(name_key.section.name(), None, name_key.name)
+ .or_else(|| fallback.as_ref().and_then(|(s, name_key, _)| s.value(name_key.name)))
+ .map(|v| v.into_owned()),
+ config
+ .string(email_key.section.name(), None, email_key.name)
+ .or_else(|| fallback.as_ref().and_then(|(s, _, email_key)| s.value(email_key.name)))
+ .map(|v| v.into_owned()),
+ )
+ }
+ let now = SystemTime::now();
+ let parse_date = |key: &str, date: &keys::Time| -> Option<Result<gix_date::Time, gix_date::parse::Error>> {
+ debug_assert_eq!(
+ key,
+ date.logical_name(),
+ "BUG: drift of expected name and actual name of the key (we hardcode it to save an allocation)"
+ );
+ config
+ .string_by_key(key)
+ .map(|time| date.try_into_time(time, now.into()))
+ };
+
+ let fallback = (
+ &gitoxide::Committer::NAME_FALLBACK,
+ &gitoxide::Committer::EMAIL_FALLBACK,
+ );
+ let (committer_name, committer_email) =
+ entity_in_section(config, &Committer::NAME, &Committer::EMAIL, Some(fallback));
+ let fallback = (&gitoxide::Author::NAME_FALLBACK, &gitoxide::Author::EMAIL_FALLBACK);
+ let (author_name, author_email) = entity_in_section(config, &Author::NAME, &Author::EMAIL, Some(fallback));
+ let (user_name, mut user_email) = entity_in_section(config, &User::NAME, &User::EMAIL, None);
+
+ let committer_date = parse_date("gitoxide.commit.committerDate", &gitoxide::Commit::COMMITTER_DATE);
+ let author_date = parse_date("gitoxide.commit.authorDate", &gitoxide::Commit::AUTHOR_DATE);
+
+ user_email = user_email.or_else(|| {
+ config
+ .string_by_key(gitoxide::User::EMAIL_FALLBACK.logical_name().as_str())
+ .map(|v| v.into_owned())
+ });
+ Personas {
+ user: Entity {
+ name: user_name,
+ email: user_email,
+ time: None,
+ },
+ committer: Entity {
+ name: committer_name,
+ email: committer_email,
+ time: committer_date,
+ },
+ author: Entity {
+ name: author_name,
+ email: author_email,
+ time: author_date,
+ },
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/impls.rs b/vendor/gix/src/repository/impls.rs
new file mode 100644
index 000000000..6cf2b2e9b
--- /dev/null
+++ b/vendor/gix/src/repository/impls.rs
@@ -0,0 +1,73 @@
+impl Clone for crate::Repository {
+ fn clone(&self) -> Self {
+ crate::Repository::from_refs_and_objects(
+ self.refs.clone(),
+ self.objects.clone(),
+ self.work_tree.clone(),
+ self.common_dir.clone(),
+ self.config.clone(),
+ self.options.clone(),
+ self.index.clone(),
+ )
+ }
+}
+
+impl std::fmt::Debug for crate::Repository {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Repository")
+ .field("kind", &self.kind())
+ .field("git_dir", &self.git_dir())
+ .field("work_dir", &self.work_dir())
+ .finish()
+ }
+}
+
+impl PartialEq<crate::Repository> for crate::Repository {
+ fn eq(&self, other: &crate::Repository) -> bool {
+ self.git_dir().canonicalize().ok() == other.git_dir().canonicalize().ok()
+ && self.work_tree.as_deref().and_then(|wt| wt.canonicalize().ok())
+ == other.work_tree.as_deref().and_then(|wt| wt.canonicalize().ok())
+ }
+}
+
+impl From<&crate::ThreadSafeRepository> for crate::Repository {
+ fn from(repo: &crate::ThreadSafeRepository) -> Self {
+ crate::Repository::from_refs_and_objects(
+ repo.refs.clone(),
+ repo.objects.to_handle().into(),
+ repo.work_tree.clone(),
+ repo.common_dir.clone(),
+ repo.config.clone(),
+ repo.linked_worktree_options.clone(),
+ repo.index.clone(),
+ )
+ }
+}
+
+impl From<crate::ThreadSafeRepository> for crate::Repository {
+ fn from(repo: crate::ThreadSafeRepository) -> Self {
+ crate::Repository::from_refs_and_objects(
+ repo.refs,
+ repo.objects.to_handle().into(),
+ repo.work_tree,
+ repo.common_dir,
+ repo.config,
+ repo.linked_worktree_options,
+ repo.index,
+ )
+ }
+}
+
+impl From<crate::Repository> for crate::ThreadSafeRepository {
+ fn from(r: crate::Repository) -> Self {
+ crate::ThreadSafeRepository {
+ refs: r.refs,
+ objects: r.objects.into_inner().store(),
+ work_tree: r.work_tree,
+ common_dir: r.common_dir,
+ config: r.config,
+ linked_worktree_options: r.options,
+ index: r.index,
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/init.rs b/vendor/gix/src/repository/init.rs
new file mode 100644
index 000000000..ae6a42c3b
--- /dev/null
+++ b/vendor/gix/src/repository/init.rs
@@ -0,0 +1,55 @@
+use std::cell::RefCell;
+
+impl crate::Repository {
+ pub(crate) fn from_refs_and_objects(
+ refs: crate::RefStore,
+ objects: crate::OdbHandle,
+ work_tree: Option<std::path::PathBuf>,
+ common_dir: Option<std::path::PathBuf>,
+ config: crate::config::Cache,
+ linked_worktree_options: crate::open::Options,
+ index: crate::worktree::IndexStorage,
+ ) -> Self {
+ let objects = setup_objects(objects, &config);
+ crate::Repository {
+ bufs: RefCell::new(Vec::with_capacity(4)),
+ work_tree,
+ common_dir,
+ objects,
+ refs,
+ config,
+ options: linked_worktree_options,
+ index,
+ }
+ }
+
+ /// Convert this instance into a [`ThreadSafeRepository`][crate::ThreadSafeRepository] by dropping all thread-local data.
+ pub fn into_sync(self) -> crate::ThreadSafeRepository {
+ self.into()
+ }
+}
+
+#[cfg_attr(not(feature = "max-performance-safe"), allow(unused_variables, unused_mut))]
+fn setup_objects(mut objects: crate::OdbHandle, config: &crate::config::Cache) -> crate::OdbHandle {
+ #[cfg(feature = "max-performance-safe")]
+ {
+ match config.pack_cache_bytes {
+ None => objects.set_pack_cache(|| Box::<gix_pack::cache::lru::StaticLinkedList<64>>::default()),
+ Some(0) => objects.unset_pack_cache(),
+ Some(bytes) => objects.set_pack_cache(move || -> Box<gix_odb::cache::PackCache> {
+ Box::new(gix_pack::cache::lru::MemoryCappedHashmap::new(bytes))
+ }),
+ };
+ if config.object_cache_bytes == 0 {
+ objects.unset_object_cache();
+ } else {
+ let bytes = config.object_cache_bytes;
+ objects.set_object_cache(move || Box::new(gix_pack::cache::object::MemoryCappedHashmap::new(bytes)));
+ }
+ objects
+ }
+ #[cfg(not(feature = "max-performance-safe"))]
+ {
+ objects
+ }
+}
diff --git a/vendor/gix/src/repository/location.rs b/vendor/gix/src/repository/location.rs
new file mode 100644
index 000000000..0bb8ea253
--- /dev/null
+++ b/vendor/gix/src/repository/location.rs
@@ -0,0 +1,86 @@
+use std::path::PathBuf;
+
+use gix_path::realpath::MAX_SYMLINKS;
+
+impl crate::Repository {
+ /// Return the path to the repository itself, containing objects, references, configuration, and more.
+ ///
+ /// Synonymous to [`path()`][crate::Repository::path()].
+ pub fn git_dir(&self) -> &std::path::Path {
+ self.refs.git_dir()
+ }
+
+ /// The trust we place in the git-dir, with lower amounts of trust causing access to configuration to be limited.
+ pub fn git_dir_trust(&self) -> gix_sec::Trust {
+ self.options.git_dir_trust.expect("definitely set by now")
+ }
+
+ /// Returns the main git repository if this is a repository on a linked work-tree, or the `git_dir` itself.
+ pub fn common_dir(&self) -> &std::path::Path {
+ self.common_dir.as_deref().unwrap_or_else(|| self.git_dir())
+ }
+
+ /// Return the path to the worktree index file, which may or may not exist.
+ pub fn index_path(&self) -> PathBuf {
+ self.git_dir().join("index")
+ }
+
+ /// The path to the `.git` directory itself, or equivalent if this is a bare repository.
+ pub fn path(&self) -> &std::path::Path {
+ self.git_dir()
+ }
+
+ /// Return the work tree containing all checked out files, if there is one.
+ pub fn work_dir(&self) -> Option<&std::path::Path> {
+ self.work_tree.as_deref()
+ }
+
+ // TODO: tests, respect precomposeUnicode
+ /// The directory of the binary path of the current process.
+ pub fn install_dir(&self) -> std::io::Result<PathBuf> {
+ crate::path::install_dir()
+ }
+
+ /// Returns the relative path which is the components between the working tree and the current working dir (CWD).
+ /// Note that there may be `None` if there is no work tree, even though the `PathBuf` will be empty
+ /// if the CWD is at the root of the work tree.
+ // TODO: tests, details - there is a lot about environment variables to change things around.
+ pub fn prefix(&self) -> Option<std::io::Result<PathBuf>> {
+ self.work_tree.as_ref().map(|root| {
+ std::env::current_dir().and_then(|cwd| {
+ gix_path::realpath_opts(root, &cwd, MAX_SYMLINKS)
+ .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))
+ .and_then(|root| {
+ cwd.strip_prefix(&root)
+ .map_err(|_| {
+ std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!(
+ "CWD '{}' isn't within the work tree '{}'",
+ cwd.display(),
+ root.display()
+ ),
+ )
+ })
+ .map(ToOwned::to_owned)
+ })
+ })
+ })
+ }
+
+ /// Return the kind of repository, either bare or one with a work tree.
+ pub fn kind(&self) -> crate::Kind {
+ match self.worktree() {
+ Some(wt) => {
+ if gix_discover::is_submodule_git_dir(self.git_dir()) {
+ crate::Kind::Submodule
+ } else {
+ crate::Kind::WorkTree {
+ is_linked: !wt.is_main(),
+ }
+ }
+ }
+ None => crate::Kind::Bare,
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/mod.rs b/vendor/gix/src/repository/mod.rs
new file mode 100644
index 000000000..31199e22d
--- /dev/null
+++ b/vendor/gix/src/repository/mod.rs
@@ -0,0 +1,36 @@
+//!
+
+/// Internal
+impl crate::Repository {
+ #[inline]
+ pub(crate) fn free_buf(&self) -> Vec<u8> {
+ self.bufs.borrow_mut().pop().unwrap_or_default()
+ }
+
+ /// This method is commonly called from the destructor of objects that previously claimed an entry
+ /// in the free-list with `free_buf()`.
+ /// They are welcome to take out the data themselves, for instance when the object is detached, to avoid
+ /// it to be reclaimed.
+ #[inline]
+ pub(crate) fn reuse_buffer(&self, data: &mut Vec<u8>) {
+ if data.capacity() > 0 {
+ self.bufs.borrow_mut().push(std::mem::take(data));
+ }
+ }
+}
+
+mod cache;
+mod config;
+pub(crate) mod identity;
+mod impls;
+mod init;
+mod location;
+mod object;
+pub(crate) mod permissions;
+mod reference;
+mod remote;
+mod revision;
+mod snapshots;
+mod state;
+mod thread_safe;
+mod worktree;
diff --git a/vendor/gix/src/repository/object.rs b/vendor/gix/src/repository/object.rs
new file mode 100644
index 000000000..bda1a54c3
--- /dev/null
+++ b/vendor/gix/src/repository/object.rs
@@ -0,0 +1,214 @@
+#![allow(clippy::result_large_err)]
+use std::convert::TryInto;
+
+use gix_hash::ObjectId;
+use gix_odb::{Find, FindExt, Write};
+use gix_ref::{
+ transaction::{LogChange, PreviousValue, RefLog},
+ FullName,
+};
+
+use crate::{commit, ext::ObjectIdExt, object, tag, Id, Object, Reference, Tree};
+
+/// Methods related to object creation.
+impl crate::Repository {
+ /// Find the object with `id` in the object database or return an error if it could not be found.
+ ///
+ /// There are various legitimate reasons for an object to not be present, which is why
+ /// [`try_find_object(…)`][crate::Repository::try_find_object()] might be preferable instead.
+ ///
+ /// # Performance Note
+ ///
+ /// In order to get the kind of the object, is must be fully decoded from storage if it is packed with deltas.
+ /// Loose object could be partially decoded, even though that's not implemented.
+ pub fn find_object(&self, id: impl Into<ObjectId>) -> Result<Object<'_>, object::find::existing::Error> {
+ let id = id.into();
+ if id == gix_hash::ObjectId::empty_tree(self.object_hash()) {
+ return Ok(Object {
+ id,
+ kind: gix_object::Kind::Tree,
+ data: Vec::new(),
+ repo: self,
+ });
+ }
+ let mut buf = self.free_buf();
+ let kind = self.objects.find(id, &mut buf)?.kind;
+ Ok(Object::from_data(id, kind, buf, self))
+ }
+
+ /// Try to find the object with `id` or return `None` it it wasn't found.
+ pub fn try_find_object(&self, id: impl Into<ObjectId>) -> Result<Option<Object<'_>>, object::find::Error> {
+ let id = id.into();
+ if id == gix_hash::ObjectId::empty_tree(self.object_hash()) {
+ return Ok(Some(Object {
+ id,
+ kind: gix_object::Kind::Tree,
+ data: Vec::new(),
+ repo: self,
+ }));
+ }
+
+ let mut buf = self.free_buf();
+ match self.objects.try_find(id, &mut buf)? {
+ Some(obj) => {
+ let kind = obj.kind;
+ Ok(Some(Object::from_data(id, kind, buf, self)))
+ }
+ None => Ok(None),
+ }
+ }
+
+ /// Write the given object into the object database and return its object id.
+ pub fn write_object(&self, object: impl gix_object::WriteTo) -> Result<Id<'_>, object::write::Error> {
+ self.objects
+ .write(object)
+ .map(|oid| oid.attach(self))
+ .map_err(Into::into)
+ }
+
+ /// Write a blob from the given `bytes`.
+ pub fn write_blob(&self, bytes: impl AsRef<[u8]>) -> Result<Id<'_>, object::write::Error> {
+ self.objects
+ .write_buf(gix_object::Kind::Blob, bytes.as_ref())
+ .map(|oid| oid.attach(self))
+ }
+
+ /// Write a blob from the given `Read` implementation.
+ pub fn write_blob_stream(
+ &self,
+ mut bytes: impl std::io::Read + std::io::Seek,
+ ) -> Result<Id<'_>, object::write::Error> {
+ let current = bytes.stream_position()?;
+ let len = bytes.seek(std::io::SeekFrom::End(0))? - current;
+ bytes.seek(std::io::SeekFrom::Start(current))?;
+
+ self.objects
+ .write_stream(gix_object::Kind::Blob, len, bytes)
+ .map(|oid| oid.attach(self))
+ }
+
+ /// Create a tag reference named `name` (without `refs/tags/` prefix) pointing to a newly created tag object
+ /// which in turn points to `target` and return the newly created reference.
+ ///
+ /// It will be created with `constraint` which is most commonly to [only create it][PreviousValue::MustNotExist]
+ /// or to [force overwriting a possibly existing tag](PreviousValue::Any).
+ pub fn tag(
+ &self,
+ name: impl AsRef<str>,
+ target: impl AsRef<gix_hash::oid>,
+ target_kind: gix_object::Kind,
+ tagger: Option<gix_actor::SignatureRef<'_>>,
+ message: impl AsRef<str>,
+ constraint: PreviousValue,
+ ) -> Result<Reference<'_>, tag::Error> {
+ let tag = gix_object::Tag {
+ target: target.as_ref().into(),
+ target_kind,
+ name: name.as_ref().into(),
+ tagger: tagger.map(|t| t.to_owned()),
+ message: message.as_ref().into(),
+ pgp_signature: None,
+ };
+ let tag_id = self.write_object(&tag)?;
+ self.tag_reference(name, tag_id, constraint).map_err(Into::into)
+ }
+
+ /// Similar to [`commit(…)`][crate::Repository::commit()], but allows to create the commit with `committer` and `author` specified.
+ ///
+ /// This forces setting the commit time and author time by hand. Note that typically, committer and author are the same.
+ pub fn commit_as<'a, 'c, Name, E>(
+ &self,
+ committer: impl Into<gix_actor::SignatureRef<'c>>,
+ author: impl Into<gix_actor::SignatureRef<'a>>,
+ reference: Name,
+ message: impl AsRef<str>,
+ tree: impl Into<ObjectId>,
+ parents: impl IntoIterator<Item = impl Into<ObjectId>>,
+ ) -> Result<Id<'_>, commit::Error>
+ where
+ Name: TryInto<FullName, Error = E>,
+ commit::Error: From<E>,
+ {
+ use gix_ref::{
+ transaction::{Change, RefEdit},
+ Target,
+ };
+
+ // TODO: possibly use CommitRef to save a few allocations (but will have to allocate for object ids anyway.
+ // This can be made vastly more efficient though if we wanted to, so we lie in the API
+ let reference = reference.try_into()?;
+ let commit = gix_object::Commit {
+ message: message.as_ref().into(),
+ tree: tree.into(),
+ author: author.into().to_owned(),
+ committer: committer.into().to_owned(),
+ encoding: None,
+ parents: parents.into_iter().map(|id| id.into()).collect(),
+ extra_headers: Default::default(),
+ };
+
+ let commit_id = self.write_object(&commit)?;
+ self.edit_reference(RefEdit {
+ change: Change::Update {
+ log: LogChange {
+ mode: RefLog::AndReference,
+ force_create_reflog: false,
+ message: crate::reference::log::message("commit", commit.message.as_ref(), commit.parents.len()),
+ },
+ expected: match commit.parents.first().map(|p| Target::Peeled(*p)) {
+ Some(previous) => {
+ if reference.as_bstr() == "HEAD" {
+ PreviousValue::MustExistAndMatch(previous)
+ } else {
+ PreviousValue::ExistingMustMatch(previous)
+ }
+ }
+ None => PreviousValue::MustNotExist,
+ },
+ new: Target::Peeled(commit_id.inner),
+ },
+ name: reference,
+ deref: true,
+ })?;
+ Ok(commit_id)
+ }
+
+ /// Create a new commit object with `message` referring to `tree` with `parents`, and point `reference`
+ /// to it. The commit is written without message encoding field, which can be assumed to be UTF-8.
+ /// `author` and `committer` fields are pre-set from the configuration, which can be altered
+ /// [temporarily][crate::Repository::config_snapshot_mut()] before the call if required.
+ ///
+ /// `reference` will be created if it doesn't exist, and can be `"HEAD"` to automatically write-through to the symbolic reference
+ /// that `HEAD` points to if it is not detached. For this reason, detached head states cannot be created unless the `HEAD` is detached
+ /// already. The reflog will be written as canonical git would do, like `<operation> (<detail>): <summary>`.
+ ///
+ /// The first parent id in `parents` is expected to be the current target of `reference` and the operation will fail if it is not.
+ /// If there is no parent, the `reference` is expected to not exist yet.
+ ///
+ /// The method fails immediately if a `reference` lock can't be acquired.
+ pub fn commit<Name, E>(
+ &self,
+ reference: Name,
+ message: impl AsRef<str>,
+ tree: impl Into<ObjectId>,
+ parents: impl IntoIterator<Item = impl Into<ObjectId>>,
+ ) -> Result<Id<'_>, commit::Error>
+ where
+ Name: TryInto<FullName, Error = E>,
+ commit::Error: From<E>,
+ {
+ let author = self.author().ok_or(commit::Error::AuthorMissing)??;
+ let committer = self.committer().ok_or(commit::Error::CommitterMissing)??;
+ self.commit_as(committer, author, reference, message, tree, parents)
+ }
+
+ /// Return an empty tree object, suitable for [getting changes](crate::Tree::changes()).
+ ///
+ /// Note that it is special and doesn't physically exist in the object database even though it can be returned.
+ /// This means that this object can be used in an uninitialized, empty repository which would report to have no objects at all.
+ pub fn empty_tree(&self) -> Tree<'_> {
+ self.find_object(gix_hash::ObjectId::empty_tree(self.object_hash()))
+ .expect("always present")
+ .into_tree()
+ }
+}
diff --git a/vendor/gix/src/repository/permissions.rs b/vendor/gix/src/repository/permissions.rs
new file mode 100644
index 000000000..88b61b739
--- /dev/null
+++ b/vendor/gix/src/repository/permissions.rs
@@ -0,0 +1,168 @@
+use gix_sec::Trust;
+
+/// Permissions associated with various resources of a git repository
+#[derive(Debug, Clone)]
+pub struct Permissions {
+ /// Permissions related to the environment
+ pub env: Environment,
+ /// Permissions related to the handling of git configuration.
+ pub config: Config,
+}
+
+/// Configure from which sources git configuration may be loaded.
+///
+/// Note that configuration from inside of the repository is always loaded as it's definitely required for correctness.
+#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)]
+pub struct Config {
+ /// The git binary may come with configuration as part of its configuration, and if this is true (default false)
+ /// we will load the configuration of the git binary, if present and not a duplicate of the ones below.
+ ///
+ /// It's disable by default as it involves executing the git binary once per execution of the application.
+ pub git_binary: bool,
+ /// Whether to use the system configuration.
+ /// This is defined as `$(prefix)/etc/gitconfig` on unix.
+ pub system: bool,
+ /// Whether to use the git application configuration.
+ ///
+ /// A platform defined location for where a user's git application configuration should be located.
+ /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used
+ /// on unix.
+ pub git: bool,
+ /// Whether to use the user configuration.
+ /// This is usually `~/.gitconfig` on unix.
+ pub user: bool,
+ /// Whether to use the configuration from environment variables.
+ pub env: bool,
+ /// Whether to follow include files are encountered in loaded configuration,
+ /// via `include` and `includeIf` sections.
+ pub includes: bool,
+}
+
+impl Config {
+ /// Allow everything which usually relates to a fully trusted environment
+ pub fn all() -> Self {
+ Config {
+ git_binary: false,
+ system: true,
+ git: true,
+ user: true,
+ env: true,
+ includes: true,
+ }
+ }
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self::all()
+ }
+}
+
+/// Permissions related to the usage of environment variables
+#[derive(Debug, Clone)]
+pub struct Environment {
+ /// Control whether resources pointed to by `XDG_CONFIG_HOME` can be used when looking up common configuration values.
+ ///
+ /// Note that [`gix_sec::Permission::Forbid`] will cause the operation to abort if a resource is set via the XDG config environment.
+ pub xdg_config_home: gix_sec::Permission,
+ /// Control the way resources pointed to by the home directory (similar to `xdg_config_home`) may be used.
+ pub home: gix_sec::Permission,
+ /// Control if environment variables to configure the HTTP transport, like `http_proxy` may be used.
+ ///
+ /// Note that http-transport related environment variables prefixed with `GIT_` may also be included here
+ /// if they match this category like `GIT_HTTP_USER_AGENT`.
+ pub http_transport: gix_sec::Permission,
+ /// Control if the `EMAIL` environment variables may be read.
+ ///
+ /// Note that identity related environment variables prefixed with `GIT_` may also be included here
+ /// if they match this category.
+ pub identity: gix_sec::Permission,
+ /// Control if environment variables related to the object database are handled. This includes features and performance
+ /// options alike.
+ pub objects: gix_sec::Permission,
+ /// Control if resources pointed to by `GIT_*` prefixed environment variables can be used, **but only** if they
+ /// are not contained in any other category. This is a catch-all section.
+ pub git_prefix: gix_sec::Permission,
+ /// Control if resources pointed to by `SSH_*` prefixed environment variables can be used (like `SSH_ASKPASS`)
+ pub ssh_prefix: gix_sec::Permission,
+}
+
+impl Environment {
+ /// Allow access to the entire environment.
+ pub fn all() -> Self {
+ let allow = gix_sec::Permission::Allow;
+ Environment {
+ xdg_config_home: allow,
+ home: allow,
+ git_prefix: allow,
+ ssh_prefix: allow,
+ http_transport: allow,
+ identity: allow,
+ objects: allow,
+ }
+ }
+}
+
+impl Permissions {
+ /// Return permissions that will not include configuration files not owned by the current user,
+ /// but trust system and global configuration files along with those which are owned by the current user.
+ ///
+ /// This allows to read and write repositories even if they aren't owned by the current user, but avoid using
+ /// anything else that could cause us to write into unknown locations or use programs beyond our `PATH`.
+ pub fn secure() -> Self {
+ Permissions {
+ env: Environment::all(),
+ config: Config::all(),
+ }
+ }
+
+ /// Everything is allowed with this set of permissions, thus we read all configuration and do what git typically
+ /// does with owned repositories.
+ pub fn all() -> Self {
+ Permissions {
+ env: Environment::all(),
+ config: Config::all(),
+ }
+ }
+
+ /// Don't read any but the local git configuration and deny reading any environment variables.
+ pub fn isolated() -> Self {
+ Permissions {
+ config: Config {
+ git_binary: false,
+ system: false,
+ git: false,
+ user: false,
+ env: false,
+ includes: false,
+ },
+ env: {
+ let deny = gix_sec::Permission::Deny;
+ Environment {
+ xdg_config_home: deny,
+ home: deny,
+ ssh_prefix: deny,
+ git_prefix: deny,
+ http_transport: deny,
+ identity: deny,
+ objects: deny,
+ }
+ },
+ }
+ }
+}
+
+impl gix_sec::trust::DefaultForLevel for Permissions {
+ fn default_for_level(level: Trust) -> Self {
+ match level {
+ Trust::Full => Permissions::all(),
+ Trust::Reduced => Permissions::secure(),
+ }
+ }
+}
+
+impl Default for Permissions {
+ fn default() -> Self {
+ Permissions::secure()
+ }
+}
diff --git a/vendor/gix/src/repository/reference.rs b/vendor/gix/src/repository/reference.rs
new file mode 100644
index 000000000..e5a8aadcb
--- /dev/null
+++ b/vendor/gix/src/repository/reference.rs
@@ -0,0 +1,243 @@
+use std::convert::TryInto;
+
+use gix_hash::ObjectId;
+use gix_ref::{
+ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog},
+ FullName, PartialNameRef, Target,
+};
+
+use crate::{bstr::BString, ext::ReferenceExt, reference, Reference};
+
+/// Obtain and alter references comfortably
+impl crate::Repository {
+ /// Create a lightweight tag with given `name` (and without `refs/tags/` prefix) pointing to the given `target`, and return it as reference.
+ ///
+ /// It will be created with `constraint` which is most commonly to [only create it][PreviousValue::MustNotExist]
+ /// or to [force overwriting a possibly existing tag](PreviousValue::Any).
+ pub fn tag_reference(
+ &self,
+ name: impl AsRef<str>,
+ target: impl Into<ObjectId>,
+ constraint: PreviousValue,
+ ) -> Result<Reference<'_>, reference::edit::Error> {
+ let id = target.into();
+ let mut edits = self.edit_reference(RefEdit {
+ change: Change::Update {
+ log: Default::default(),
+ expected: constraint,
+ new: Target::Peeled(id),
+ },
+ name: format!("refs/tags/{}", name.as_ref()).try_into()?,
+ deref: false,
+ })?;
+ assert_eq!(edits.len(), 1, "reference splits should ever happen");
+ let edit = edits.pop().expect("exactly one item");
+ Ok(Reference {
+ inner: gix_ref::Reference {
+ name: edit.name,
+ target: id.into(),
+ peeled: None,
+ },
+ repo: self,
+ })
+ }
+
+ /// Returns the currently set namespace for references, or `None` if it is not set.
+ ///
+ /// Namespaces allow to partition references, and is configured per `Easy`.
+ pub fn namespace(&self) -> Option<&gix_ref::Namespace> {
+ self.refs.namespace.as_ref()
+ }
+
+ /// Remove the currently set reference namespace and return it, affecting only this `Easy`.
+ pub fn clear_namespace(&mut self) -> Option<gix_ref::Namespace> {
+ self.refs.namespace.take()
+ }
+
+ /// Set the reference namespace to the given value, like `"foo"` or `"foo/bar"`.
+ ///
+ /// Note that this value is shared across all `Easy…` instances as the value is stored in the shared `Repository`.
+ pub fn set_namespace<'a, Name, E>(
+ &mut self,
+ namespace: Name,
+ ) -> Result<Option<gix_ref::Namespace>, gix_validate::refname::Error>
+ where
+ Name: TryInto<&'a PartialNameRef, Error = E>,
+ gix_validate::refname::Error: From<E>,
+ {
+ let namespace = gix_ref::namespace::expand(namespace)?;
+ Ok(self.refs.namespace.replace(namespace))
+ }
+
+ // TODO: more tests or usage
+ /// Create a new reference with `name`, like `refs/heads/branch`, pointing to `target`, adhering to `constraint`
+ /// during creation and writing `log_message` into the reflog. Note that a ref-log will be written even if `log_message` is empty.
+ ///
+ /// The newly created Reference is returned.
+ pub fn reference<Name, E>(
+ &self,
+ name: Name,
+ target: impl Into<ObjectId>,
+ constraint: PreviousValue,
+ log_message: impl Into<BString>,
+ ) -> Result<Reference<'_>, reference::edit::Error>
+ where
+ Name: TryInto<FullName, Error = E>,
+ gix_validate::reference::name::Error: From<E>,
+ {
+ let name = name.try_into().map_err(gix_validate::reference::name::Error::from)?;
+ let id = target.into();
+ let mut edits = self.edit_reference(RefEdit {
+ change: Change::Update {
+ log: LogChange {
+ mode: RefLog::AndReference,
+ force_create_reflog: false,
+ message: log_message.into(),
+ },
+ expected: constraint,
+ new: Target::Peeled(id),
+ },
+ name,
+ deref: false,
+ })?;
+ assert_eq!(
+ edits.len(),
+ 1,
+ "only one reference can be created, splits aren't possible"
+ );
+
+ Ok(gix_ref::Reference {
+ name: edits.pop().expect("exactly one edit").name,
+ target: Target::Peeled(id),
+ peeled: None,
+ }
+ .attach(self))
+ }
+
+ /// Edit a single reference as described in `edit`, and write reference logs as `log_committer`.
+ ///
+ /// One or more `RefEdit`s are returned - symbolic reference splits can cause more edits to be performed. All edits have the previous
+ /// reference values set to the ones encountered at rest after acquiring the respective reference's lock.
+ pub fn edit_reference(&self, edit: RefEdit) -> Result<Vec<RefEdit>, reference::edit::Error> {
+ self.edit_references(Some(edit))
+ }
+
+ /// Edit one or more references as described by their `edits`.
+ /// Note that one can set the committer name for use in the ref-log by temporarily
+ /// [overriding the gix-config][crate::Repository::config_snapshot_mut()].
+ ///
+ /// Returns all reference edits, which might be more than where provided due the splitting of symbolic references, and
+ /// whose previous (_old_) values are the ones seen on in storage after the reference was locked.
+ pub fn edit_references(
+ &self,
+ edits: impl IntoIterator<Item = RefEdit>,
+ ) -> Result<Vec<RefEdit>, reference::edit::Error> {
+ let (file_lock_fail, packed_refs_lock_fail) = self.config.lock_timeout()?;
+ self.refs
+ .transaction()
+ .prepare(edits, file_lock_fail, packed_refs_lock_fail)?
+ .commit(self.committer().transpose()?)
+ .map_err(Into::into)
+ }
+
+ /// Return the repository head, an abstraction to help dealing with the `HEAD` reference.
+ ///
+ /// The `HEAD` reference can be in various states, for more information, the documentation of [`Head`][crate::Head].
+ pub fn head(&self) -> Result<crate::Head<'_>, reference::find::existing::Error> {
+ let head = self.find_reference("HEAD")?;
+ Ok(match head.inner.target {
+ Target::Symbolic(branch) => match self.find_reference(&branch) {
+ Ok(r) => crate::head::Kind::Symbolic(r.detach()),
+ Err(reference::find::existing::Error::NotFound) => crate::head::Kind::Unborn(branch),
+ Err(err) => return Err(err),
+ },
+ Target::Peeled(target) => crate::head::Kind::Detached {
+ target,
+ peeled: head.inner.peeled,
+ },
+ }
+ .attach(self))
+ }
+
+ /// Resolve the `HEAD` reference, follow and peel its target and obtain its object id.
+ ///
+ /// Note that this may fail for various reasons, most notably because the repository
+ /// is freshly initialized and doesn't have any commits yet.
+ ///
+ /// Also note that the returned id is likely to point to a commit, but could also
+ /// point to a tree or blob. It won't, however, point to a tag as these are always peeled.
+ pub fn head_id(&self) -> Result<crate::Id<'_>, reference::head_id::Error> {
+ let mut head = self.head()?;
+ head.peel_to_id_in_place()
+ .ok_or_else(|| reference::head_id::Error::Unborn {
+ name: head.referent_name().expect("unborn").to_owned(),
+ })?
+ .map_err(Into::into)
+ }
+
+ /// Return the name to the symbolic reference `HEAD` points to, or `None` if the head is detached.
+ ///
+ /// The difference to [`head_ref()`][Self::head_ref()] is that the latter requires the reference to exist,
+ /// whereas here we merely return a the name of the possibly unborn reference.
+ pub fn head_name(&self) -> Result<Option<FullName>, reference::find::existing::Error> {
+ Ok(self.head()?.referent_name().map(|n| n.to_owned()))
+ }
+
+ /// Return the reference that `HEAD` points to, or `None` if the head is detached or unborn.
+ pub fn head_ref(&self) -> Result<Option<Reference<'_>>, reference::find::existing::Error> {
+ Ok(self.head()?.try_into_referent())
+ }
+
+ /// Return the commit object the `HEAD` reference currently points to after peeling it fully.
+ ///
+ /// Note that this may fail for various reasons, most notably because the repository
+ /// is freshly initialized and doesn't have any commits yet. It could also fail if the
+ /// head does not point to a commit.
+ pub fn head_commit(&self) -> Result<crate::Commit<'_>, reference::head_commit::Error> {
+ Ok(self.head()?.peel_to_commit_in_place()?)
+ }
+
+ /// Find the reference with the given partial or full `name`, like `main`, `HEAD`, `heads/branch` or `origin/other`,
+ /// or return an error if it wasn't found.
+ ///
+ /// Consider [`try_find_reference(…)`][crate::Repository::try_find_reference()] if the reference might not exist
+ /// without that being considered an error.
+ pub fn find_reference<'a, Name, E>(&self, name: Name) -> Result<Reference<'_>, reference::find::existing::Error>
+ where
+ Name: TryInto<&'a PartialNameRef, Error = E>,
+ gix_ref::file::find::Error: From<E>,
+ {
+ self.try_find_reference(name)?
+ .ok_or(reference::find::existing::Error::NotFound)
+ }
+
+ /// Return a platform for iterating references.
+ ///
+ /// Common kinds of iteration are [all][crate::reference::iter::Platform::all()] or [prefixed][crate::reference::iter::Platform::prefixed()]
+ /// references.
+ pub fn references(&self) -> Result<reference::iter::Platform<'_>, reference::iter::Error> {
+ Ok(reference::iter::Platform {
+ platform: self.refs.iter()?,
+ repo: self,
+ })
+ }
+
+ /// Try to find the reference named `name`, like `main`, `heads/branch`, `HEAD` or `origin/other`, and return it.
+ ///
+ /// Otherwise return `None` if the reference wasn't found.
+ /// If the reference is expected to exist, use [`find_reference()`][crate::Repository::find_reference()].
+ pub fn try_find_reference<'a, Name, E>(&self, name: Name) -> Result<Option<Reference<'_>>, reference::find::Error>
+ where
+ Name: TryInto<&'a PartialNameRef, Error = E>,
+ gix_ref::file::find::Error: From<E>,
+ {
+ let state = self;
+ match state.refs.try_find(name) {
+ Ok(r) => match r {
+ Some(r) => Ok(Some(Reference::from_ref(r, self))),
+ None => Ok(None),
+ },
+ Err(err) => Err(err.into()),
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/remote.rs b/vendor/gix/src/repository/remote.rs
new file mode 100644
index 000000000..e3f210899
--- /dev/null
+++ b/vendor/gix/src/repository/remote.rs
@@ -0,0 +1,199 @@
+#![allow(clippy::result_large_err)]
+use std::convert::TryInto;
+
+use crate::{bstr::BStr, config, remote, remote::find, Remote};
+
+impl crate::Repository {
+ /// Create a new remote available at the given `url`.
+ ///
+ /// It's configured to fetch included tags by default, similar to git.
+ /// See [`with_fetch_tags(…)`][Remote::with_fetch_tags()] for a way to change it.
+ pub fn remote_at<Url, E>(&self, url: Url) -> Result<Remote<'_>, remote::init::Error>
+ where
+ Url: TryInto<gix_url::Url, Error = E>,
+ gix_url::parse::Error: From<E>,
+ {
+ Remote::from_fetch_url(url, true, self)
+ }
+
+ /// Create a new remote available at the given `url` similarly to [`remote_at()`][crate::Repository::remote_at()],
+ /// but don't rewrite the url according to rewrite rules.
+ /// This eliminates a failure mode in case the rewritten URL is faulty, allowing to selectively [apply rewrite
+ /// rules][Remote::rewrite_urls()] later and do so non-destructively.
+ pub fn remote_at_without_url_rewrite<Url, E>(&self, url: Url) -> Result<Remote<'_>, remote::init::Error>
+ where
+ Url: TryInto<gix_url::Url, Error = E>,
+ gix_url::parse::Error: From<E>,
+ {
+ Remote::from_fetch_url(url, false, self)
+ }
+
+ /// Find the remote with the given `name_or_url` or report an error, similar to [`try_find_remote(…)`][Self::try_find_remote()].
+ ///
+ /// Note that we will obtain remotes only if we deem them [trustworthy][crate::open::Options::filter_config_section()].
+ pub fn find_remote<'a>(&self, name_or_url: impl Into<&'a BStr>) -> Result<Remote<'_>, find::existing::Error> {
+ let name_or_url = name_or_url.into();
+ Ok(self
+ .try_find_remote(name_or_url)
+ .ok_or_else(|| find::existing::Error::NotFound {
+ name: name_or_url.into(),
+ })??)
+ }
+
+ /// Find the default remote as configured, or `None` if no such configuration could be found.
+ ///
+ /// See [remote_default_name()][Self::remote_default_name()] for more information on the `direction` parameter.
+ pub fn find_default_remote(
+ &self,
+ direction: remote::Direction,
+ ) -> Option<Result<Remote<'_>, find::existing::Error>> {
+ self.remote_default_name(direction)
+ .map(|name| self.find_remote(name.as_ref()))
+ }
+
+ /// Find the remote with the given `name_or_url` or return `None` if it doesn't exist, for the purpose of fetching or pushing
+ /// data to a remote.
+ ///
+ /// There are various error kinds related to partial information or incorrectly formatted URLs or ref-specs.
+ /// Also note that the created `Remote` may have neither fetch nor push ref-specs set at all.
+ ///
+ /// Note that ref-specs are de-duplicated right away which may change their order. This doesn't affect matching in any way
+ /// as negations/excludes are applied after includes.
+ ///
+ /// We will only include information if we deem it [trustworthy][crate::open::Options::filter_config_section()].
+ pub fn try_find_remote<'a>(&self, name_or_url: impl Into<&'a BStr>) -> Option<Result<Remote<'_>, find::Error>> {
+ self.try_find_remote_inner(name_or_url, true)
+ }
+
+ /// Similar to [try_find_remote()][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid
+ /// as it skips rewriting them.
+ /// Use this in conjunction with [`Remote::rewrite_urls()`] to non-destructively apply the rules and keep the failed urls unchanged.
+ pub fn try_find_remote_without_url_rewrite<'a>(
+ &self,
+ name_or_url: impl Into<&'a BStr>,
+ ) -> Option<Result<Remote<'_>, find::Error>> {
+ self.try_find_remote_inner(name_or_url, false)
+ }
+
+ fn try_find_remote_inner<'a>(
+ &self,
+ name_or_url: impl Into<&'a BStr>,
+ rewrite_urls: bool,
+ ) -> Option<Result<Remote<'_>, find::Error>> {
+ fn config_spec<T: config::tree::keys::Validate>(
+ specs: Vec<std::borrow::Cow<'_, BStr>>,
+ name_or_url: &BStr,
+ key: &'static config::tree::keys::Any<T>,
+ op: gix_refspec::parse::Operation,
+ ) -> Result<Vec<gix_refspec::RefSpec>, find::Error> {
+ let kind = key.name;
+ specs
+ .into_iter()
+ .map(|spec| {
+ key.try_into_refspec(spec, op).map_err(|err| find::Error::RefSpec {
+ remote_name: name_or_url.into(),
+ kind,
+ source: err,
+ })
+ })
+ .collect::<Result<Vec<_>, _>>()
+ .map(|mut specs| {
+ specs.sort();
+ specs.dedup();
+ specs
+ })
+ }
+
+ let mut filter = self.filter_config_section();
+ let name_or_url = name_or_url.into();
+ let mut config_url = |key: &'static config::tree::keys::Url, kind: &'static str| {
+ self.config
+ .resolved
+ .string_filter("remote", Some(name_or_url), key.name, &mut filter)
+ .map(|url| {
+ key.try_into_url(url).map_err(|err| find::Error::Url {
+ kind,
+ remote_name: name_or_url.into(),
+ source: err,
+ })
+ })
+ };
+ let url = config_url(&config::tree::Remote::URL, "fetch");
+ let push_url = config_url(&config::tree::Remote::PUSH_URL, "push");
+ let config = &self.config.resolved;
+
+ let fetch_specs = config
+ .strings_filter("remote", Some(name_or_url), "fetch", &mut filter)
+ .map(|specs| {
+ config_spec(
+ specs,
+ name_or_url,
+ &config::tree::Remote::FETCH,
+ gix_refspec::parse::Operation::Fetch,
+ )
+ });
+ let push_specs = config
+ .strings_filter("remote", Some(name_or_url), "push", &mut filter)
+ .map(|specs| {
+ config_spec(
+ specs,
+ name_or_url,
+ &config::tree::Remote::PUSH,
+ gix_refspec::parse::Operation::Push,
+ )
+ });
+ let fetch_tags = config
+ .string_filter("remote", Some(name_or_url), "tagOpt", &mut filter)
+ .map(|value| {
+ config::tree::Remote::TAG_OPT
+ .try_into_tag_opt(value)
+ .map_err(Into::into)
+ });
+ let fetch_tags = match fetch_tags {
+ Some(Ok(v)) => v,
+ Some(Err(err)) => return Some(Err(err)),
+ None => Default::default(),
+ };
+
+ match (url, fetch_specs, push_url, push_specs) {
+ (None, None, None, None) => None,
+ (None, _, None, _) => Some(Err(find::Error::UrlMissing)),
+ (url, fetch_specs, push_url, push_specs) => {
+ let url = match url {
+ Some(Ok(v)) => Some(v),
+ Some(Err(err)) => return Some(Err(err)),
+ None => None,
+ };
+ let push_url = match push_url {
+ Some(Ok(v)) => Some(v),
+ Some(Err(err)) => return Some(Err(err)),
+ None => None,
+ };
+ let fetch_specs = match fetch_specs {
+ Some(Ok(v)) => v,
+ Some(Err(err)) => return Some(Err(err)),
+ None => Vec::new(),
+ };
+ let push_specs = match push_specs {
+ Some(Ok(v)) => v,
+ Some(Err(err)) => return Some(Err(err)),
+ None => Vec::new(),
+ };
+
+ Some(
+ Remote::from_preparsed_config(
+ Some(name_or_url.to_owned()),
+ url,
+ push_url,
+ fetch_specs,
+ push_specs,
+ rewrite_urls,
+ fetch_tags,
+ self,
+ )
+ .map_err(Into::into),
+ )
+ }
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/revision.rs b/vendor/gix/src/repository/revision.rs
new file mode 100644
index 000000000..3018c2be8
--- /dev/null
+++ b/vendor/gix/src/repository/revision.rs
@@ -0,0 +1,42 @@
+use crate::{bstr::BStr, revision, Id};
+
+/// Methods for resolving revisions by spec or working with the commit graph.
+impl crate::Repository {
+ /// Parse a revision specification and turn it into the object(s) it describes, similar to `git rev-parse`.
+ ///
+ /// # Deviation
+ ///
+ /// - `@` actually stands for `HEAD`, whereas `git` resolves it to the object pointed to by `HEAD` without making the
+ /// `HEAD` ref available for lookups.
+ pub fn rev_parse<'a>(&self, spec: impl Into<&'a BStr>) -> Result<revision::Spec<'_>, revision::spec::parse::Error> {
+ revision::Spec::from_bstr(
+ spec,
+ self,
+ revision::spec::parse::Options {
+ object_kind_hint: self.config.object_kind_hint,
+ ..Default::default()
+ },
+ )
+ }
+
+ /// Parse a revision specification and return single object id as represented by this instance.
+ pub fn rev_parse_single<'repo, 'a>(
+ &'repo self,
+ spec: impl Into<&'a BStr>,
+ ) -> Result<Id<'repo>, revision::spec::parse::single::Error> {
+ let spec = spec.into();
+ self.rev_parse(spec)?
+ .single()
+ .ok_or(revision::spec::parse::single::Error::RangedRev { spec: spec.into() })
+ }
+
+ /// Create the baseline for a revision walk by initializing it with the `tips` to start iterating on.
+ ///
+ /// It can be configured further before starting the actual walk.
+ pub fn rev_walk(
+ &self,
+ tips: impl IntoIterator<Item = impl Into<gix_hash::ObjectId>>,
+ ) -> revision::walk::Platform<'_> {
+ revision::walk::Platform::new(tips, self)
+ }
+}
diff --git a/vendor/gix/src/repository/snapshots.rs b/vendor/gix/src/repository/snapshots.rs
new file mode 100644
index 000000000..6933dc9c6
--- /dev/null
+++ b/vendor/gix/src/repository/snapshots.rs
@@ -0,0 +1,109 @@
+impl crate::Repository {
+ // TODO: tests
+ /// Similar to [`open_mailmap_into()`][crate::Repository::open_mailmap_into()], but ignores all errors and returns at worst
+ /// an empty mailmap, e.g. if there is no mailmap or if there were errors loading them.
+ ///
+ /// This represents typical usage within git, which also works with what's there without considering a populated mailmap
+ /// a reason to abort an operation, considering it optional.
+ pub fn open_mailmap(&self) -> gix_mailmap::Snapshot {
+ let mut out = gix_mailmap::Snapshot::default();
+ self.open_mailmap_into(&mut out).ok();
+ out
+ }
+
+ // TODO: tests
+ /// Try to merge mailmaps from the following locations into `target`:
+ ///
+ /// - read the `.mailmap` file without following symlinks from the working tree, if present
+ /// - OR read `HEAD:.mailmap` if this repository is bare (i.e. has no working tree), if the `mailmap.blob` is not set.
+ /// - read the mailmap as configured in `mailmap.blob`, if set.
+ /// - read the file as configured by `mailmap.file`, following symlinks, if set.
+ ///
+ /// Only the first error will be reported, and as many source mailmaps will be merged into `target` as possible.
+ /// Parsing errors will be ignored.
+ pub fn open_mailmap_into(&self, target: &mut gix_mailmap::Snapshot) -> Result<(), crate::mailmap::load::Error> {
+ let mut err = None::<crate::mailmap::load::Error>;
+ let mut buf = Vec::new();
+ let mut blob_id = self
+ .config
+ .resolved
+ .raw_value("mailmap", None, "blob")
+ .ok()
+ .and_then(|spec| {
+ // TODO: actually resolve this as spec (once we can do that)
+ gix_hash::ObjectId::from_hex(spec.as_ref())
+ .map_err(|e| err.get_or_insert(e.into()))
+ .ok()
+ });
+ match self.work_dir() {
+ None => {
+ // TODO: replace with ref-spec `HEAD:.mailmap` for less verbose way of getting the blob id
+ blob_id = blob_id.or_else(|| {
+ self.head().ok().and_then(|mut head| {
+ let commit = head.peel_to_commit_in_place().ok()?;
+ let tree = commit.tree().ok()?;
+ tree.lookup_entry(Some(".mailmap")).ok()?.map(|e| e.object_id())
+ })
+ });
+ }
+ Some(root) => {
+ if let Ok(mut file) = gix_features::fs::open_options_no_follow()
+ .read(true)
+ .open(root.join(".mailmap"))
+ .map_err(|e| {
+ if e.kind() != std::io::ErrorKind::NotFound {
+ err.get_or_insert(e.into());
+ }
+ })
+ {
+ buf.clear();
+ std::io::copy(&mut file, &mut buf)
+ .map_err(|e| err.get_or_insert(e.into()))
+ .ok();
+ target.merge(gix_mailmap::parse_ignore_errors(&buf));
+ }
+ }
+ }
+
+ if let Some(blob) = blob_id.and_then(|id| self.find_object(id).map_err(|e| err.get_or_insert(e.into())).ok()) {
+ target.merge(gix_mailmap::parse_ignore_errors(&blob.data));
+ }
+
+ let configured_path = self
+ .config
+ .resolved
+ .value::<gix_config::Path<'_>>("mailmap", None, "file")
+ .ok()
+ .and_then(|path| {
+ let install_dir = self.install_dir().ok()?;
+ let home = self.config.home_dir();
+ match path.interpolate(gix_config::path::interpolate::Context {
+ git_install_dir: Some(install_dir.as_path()),
+ home_dir: home.as_deref(),
+ home_for_user: if self.options.git_dir_trust.expect("trust is set") == gix_sec::Trust::Full {
+ Some(gix_config::path::interpolate::home_for_user)
+ } else {
+ None
+ },
+ }) {
+ Ok(path) => Some(path),
+ Err(e) => {
+ err.get_or_insert(e.into());
+ None
+ }
+ }
+ });
+
+ if let Some(mut file) =
+ configured_path.and_then(|path| std::fs::File::open(path).map_err(|e| err.get_or_insert(e.into())).ok())
+ {
+ buf.clear();
+ std::io::copy(&mut file, &mut buf)
+ .map_err(|e| err.get_or_insert(e.into()))
+ .ok();
+ target.merge(gix_mailmap::parse_ignore_errors(&buf));
+ }
+
+ err.map(Err).unwrap_or(Ok(()))
+ }
+}
diff --git a/vendor/gix/src/repository/state.rs b/vendor/gix/src/repository/state.rs
new file mode 100644
index 000000000..4034fe349
--- /dev/null
+++ b/vendor/gix/src/repository/state.rs
@@ -0,0 +1,44 @@
+use crate::state;
+
+impl crate::Repository {
+ /// Returns the status of an in progress operation on a repository or [`None`]
+ /// if no operation is currently in progress.
+ ///
+ /// Note to be confused with the repositories 'status'.
+ pub fn state(&self) -> Option<state::InProgress> {
+ let git_dir = self.path();
+
+ // This is modeled on the logic from wt_status_get_state in git's wt-status.c and
+ // ps1 from gix-prompt.sh.
+
+ if git_dir.join("rebase-apply/applying").is_file() {
+ Some(state::InProgress::ApplyMailbox)
+ } else if git_dir.join("rebase-apply/rebasing").is_file() {
+ Some(state::InProgress::Rebase)
+ } else if git_dir.join("rebase-apply").is_dir() {
+ Some(state::InProgress::ApplyMailboxRebase)
+ } else if git_dir.join("rebase-merge/interactive").is_file() {
+ Some(state::InProgress::RebaseInteractive)
+ } else if git_dir.join("rebase-merge").is_dir() {
+ Some(state::InProgress::Rebase)
+ } else if git_dir.join("CHERRY_PICK_HEAD").is_file() {
+ if git_dir.join("sequencer/todo").is_file() {
+ Some(state::InProgress::CherryPickSequence)
+ } else {
+ Some(state::InProgress::CherryPick)
+ }
+ } else if git_dir.join("MERGE_HEAD").is_file() {
+ Some(state::InProgress::Merge)
+ } else if git_dir.join("BISECT_LOG").is_file() {
+ Some(state::InProgress::Bisect)
+ } else if git_dir.join("REVERT_HEAD").is_file() {
+ if git_dir.join("sequencer/todo").is_file() {
+ Some(state::InProgress::RevertSequence)
+ } else {
+ Some(state::InProgress::Revert)
+ }
+ } else {
+ None
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/thread_safe.rs b/vendor/gix/src/repository/thread_safe.rs
new file mode 100644
index 000000000..7c89aee60
--- /dev/null
+++ b/vendor/gix/src/repository/thread_safe.rs
@@ -0,0 +1,66 @@
+mod access {
+ use crate::Kind;
+
+ impl crate::ThreadSafeRepository {
+ /// Return the kind of repository, either bare or one with a work tree.
+ pub fn kind(&self) -> Kind {
+ match self.work_tree {
+ Some(_) => Kind::WorkTree {
+ is_linked: crate::worktree::id(self.git_dir(), self.common_dir.is_some()).is_some(),
+ },
+ None => Kind::Bare,
+ }
+ }
+
+ /// Add thread-local state to an easy-to-use thread-local repository for the most convenient API.
+ pub fn to_thread_local(&self) -> crate::Repository {
+ self.into()
+ }
+ }
+}
+
+mod location {
+
+ impl crate::ThreadSafeRepository {
+ /// The path to the `.git` directory itself, or equivalent if this is a bare repository.
+ pub fn path(&self) -> &std::path::Path {
+ self.git_dir()
+ }
+
+ /// Return the path to the repository itself, containing objects, references, configuration, and more.
+ ///
+ /// Synonymous to [`path()`][crate::ThreadSafeRepository::path()].
+ pub fn git_dir(&self) -> &std::path::Path {
+ self.refs.git_dir()
+ }
+
+ /// Return the path to the working directory if this is not a bare repository.
+ pub fn work_dir(&self) -> Option<&std::path::Path> {
+ self.work_tree.as_deref()
+ }
+
+ /// Return the path to the directory containing all objects.
+ pub fn objects_dir(&self) -> &std::path::Path {
+ self.objects.path()
+ }
+ }
+}
+
+mod impls {
+ impl std::fmt::Debug for crate::ThreadSafeRepository {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Repository(git = '{}', working_tree: {:?}",
+ self.git_dir().display(),
+ self.work_tree
+ )
+ }
+ }
+
+ impl PartialEq<crate::ThreadSafeRepository> for crate::ThreadSafeRepository {
+ fn eq(&self, other: &crate::ThreadSafeRepository) -> bool {
+ self.git_dir() == other.git_dir() && self.work_tree == other.work_tree
+ }
+ }
+}
diff --git a/vendor/gix/src/repository/worktree.rs b/vendor/gix/src/repository/worktree.rs
new file mode 100644
index 000000000..2de31bc86
--- /dev/null
+++ b/vendor/gix/src/repository/worktree.rs
@@ -0,0 +1,119 @@
+use crate::{worktree, Worktree};
+
+/// Worktree iteration
+impl crate::Repository {
+ /// Return a list of all _linked_ worktrees sorted by private git dir path as a lightweight proxy.
+ ///
+ /// Note that these need additional processing to become usable, but provide a first glimpse a typical worktree information.
+ pub fn worktrees(&self) -> std::io::Result<Vec<worktree::Proxy<'_>>> {
+ let mut res = Vec::new();
+ let iter = match std::fs::read_dir(self.common_dir().join("worktrees")) {
+ Ok(iter) => iter,
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(res),
+ Err(err) => return Err(err),
+ };
+ for entry in iter {
+ let entry = entry?;
+ let worktree_git_dir = entry.path();
+ if worktree_git_dir.join("gitdir").is_file() {
+ res.push(worktree::Proxy {
+ parent: self,
+ git_dir: worktree_git_dir,
+ })
+ }
+ }
+ res.sort_by(|a, b| a.git_dir.cmp(&b.git_dir));
+ Ok(res)
+ }
+}
+
+/// Interact with individual worktrees and their information.
+impl crate::Repository {
+ /// Return the repository owning the main worktree, typically from a linked worktree.
+ ///
+ /// Note that it might be the one that is currently open if this repository doesn't point to a linked worktree.
+ /// Also note that the main repo might be bare.
+ #[allow(clippy::result_large_err)]
+ pub fn main_repo(&self) -> Result<crate::Repository, crate::open::Error> {
+ crate::ThreadSafeRepository::open_opts(self.common_dir(), self.options.clone()).map(Into::into)
+ }
+
+ /// Return the currently set worktree if there is one, acting as platform providing a validated worktree base path.
+ ///
+ /// Note that there would be `None` if this repository is `bare` and the parent [`Repository`][crate::Repository] was instantiated without
+ /// registered worktree in the current working dir.
+ pub fn worktree(&self) -> Option<Worktree<'_>> {
+ self.work_dir().map(|path| Worktree { parent: self, path })
+ }
+
+ /// Return true if this repository is bare, and has no main work tree.
+ ///
+ /// This is not to be confused with the [`worktree()`][crate::Repository::worktree()] worktree, which may exists if this instance
+ /// was opened in a worktree that was created separately.
+ pub fn is_bare(&self) -> bool {
+ self.config.is_bare && self.work_dir().is_none()
+ }
+
+ /// Open a new copy of the index file and decode it entirely.
+ ///
+ /// It will use the `index.threads` configuration key to learn how many threads to use.
+ /// Note that it may fail if there is no index.
+ // TODO: test
+ pub fn open_index(&self) -> Result<gix_index::File, worktree::open_index::Error> {
+ let thread_limit = self
+ .config
+ .resolved
+ .boolean("index", None, "threads")
+ .map(|res| {
+ res.map(|value| usize::from(!value)).or_else(|err| {
+ gix_config::Integer::try_from(err.input.as_ref())
+ .map_err(|err| worktree::open_index::Error::ConfigIndexThreads {
+ value: err.input.clone(),
+ err,
+ })
+ .map(|value| value.to_decimal().and_then(|v| v.try_into().ok()).unwrap_or(1))
+ })
+ })
+ .transpose()?;
+ gix_index::File::at(
+ self.index_path(),
+ self.object_hash(),
+ gix_index::decode::Options {
+ thread_limit,
+ min_extension_block_in_bytes_for_threading: 0,
+ expected_checksum: None,
+ },
+ )
+ .map_err(Into::into)
+ }
+
+ /// Return a shared worktree index which is updated automatically if the in-memory snapshot has become stale as the underlying file
+ /// on disk has changed.
+ ///
+ /// The index file is shared across all clones of this repository.
+ pub fn index(&self) -> Result<worktree::Index, worktree::open_index::Error> {
+ self.index
+ .recent_snapshot(
+ || self.index_path().metadata().and_then(|m| m.modified()).ok(),
+ || {
+ self.open_index().map(Some).or_else(|err| match err {
+ worktree::open_index::Error::IndexFile(gix_index::file::init::Error::Io(err))
+ if err.kind() == std::io::ErrorKind::NotFound =>
+ {
+ Ok(None)
+ }
+ err => Err(err),
+ })
+ },
+ )
+ .and_then(|opt| match opt {
+ Some(index) => Ok(index),
+ None => Err(worktree::open_index::Error::IndexFile(
+ gix_index::file::init::Error::Io(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("Could not find index file at {:?} for opening.", self.index_path()),
+ )),
+ )),
+ })
+ }
+}