diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:44 +0000 |
commit | c23a457e72abe608715ac76f076f47dc42af07a5 (patch) | |
tree | 2772049aaf84b5c9d0ed12ec8d86812f7a7904b6 /vendor/gix/src/repository | |
parent | Releasing progress-linux version 1.73.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-c23a457e72abe608715ac76f076f47dc42af07a5.tar.xz rustc-c23a457e72abe608715ac76f076f47dc42af07a5.zip |
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix/src/repository')
20 files changed, 869 insertions, 195 deletions
diff --git a/vendor/gix/src/repository/attributes.rs b/vendor/gix/src/repository/attributes.rs index 252529761..7f747f7fd 100644 --- a/vendor/gix/src/repository/attributes.rs +++ b/vendor/gix/src/repository/attributes.rs @@ -1,5 +1,15 @@ //! exclude information -use crate::Repository; +use crate::{config, AttributeStack, Repository}; + +/// The error returned by [`Repository::attributes()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + ConfigureAttributes(#[from] config::attribute_stack::Error), + #[error(transparent)] + ConfigureExcludes(#[from] config::exclude_stack::Error), +} impl Repository { /// Configure a file-system cache for accessing git attributes *and* excludes on a per-path basis. @@ -14,15 +24,14 @@ impl Repository { /// /// * `$XDG_CONFIG_HOME/…/ignore|attributes` if `core.excludesFile|attributesFile` is *not* set, otherwise use the configured file. /// * `$GIT_DIR/info/exclude|attributes` if present. - // TODO: test, provide higher-level custom Cache wrapper that is much easier to use and doesn't panic when accessing entries - // by non-relative path. + #[cfg(feature = "attributes")] pub fn attributes( &self, index: &gix_index::State, - attributes_source: gix_worktree::cache::state::attributes::Source, - ignore_source: gix_worktree::cache::state::ignore::Source, + attributes_source: gix_worktree::stack::state::attributes::Source, + ignore_source: gix_worktree::stack::state::ignore::Source, exclude_overrides: Option<gix_ignore::Search>, - ) -> Result<gix_worktree::Cache, crate::attributes::Error> { + ) -> Result<AttributeStack<'_>, Error> { let case = if self.config.ignore_case { gix_glob::pattern::Case::Fold } else { @@ -36,15 +45,95 @@ impl Repository { let ignore = self.config .assemble_exclude_globals(self.git_dir(), exclude_overrides, ignore_source, &mut buf)?; - let state = gix_worktree::cache::State::AttributesAndIgnoreStack { attributes, ignore }; - let attribute_list = state.id_mappings_from_index(index, index.path_backing(), ignore_source, case); - Ok(gix_worktree::Cache::new( - // this is alright as we don't cause mutation of that directory, it's virtual. - self.work_dir().unwrap_or(self.git_dir()), - state, - case, - buf, - attribute_list, + let state = gix_worktree::stack::State::AttributesAndIgnoreStack { attributes, ignore }; + let attribute_list = state.id_mappings_from_index(index, index.path_backing(), case); + Ok(AttributeStack::new( + gix_worktree::Stack::new( + // this is alright as we don't cause mutation of that directory, it's virtual. + self.work_dir().unwrap_or(self.git_dir()), + state, + case, + buf, + attribute_list, + ), + self, + )) + } + + /// Like [attributes()][Self::attributes()], but without access to exclude/ignore information. + #[cfg(feature = "attributes")] + pub fn attributes_only( + &self, + index: &gix_index::State, + attributes_source: gix_worktree::stack::state::attributes::Source, + ) -> Result<AttributeStack<'_>, config::attribute_stack::Error> { + let case = if self.config.ignore_case { + gix_glob::pattern::Case::Fold + } else { + gix_glob::pattern::Case::Sensitive + }; + let (attributes, buf) = self.config.assemble_attribute_globals( + self.git_dir(), + attributes_source, + self.options.permissions.attributes, + )?; + let state = gix_worktree::stack::State::AttributesStack(attributes); + let attribute_list = state.id_mappings_from_index(index, index.path_backing(), case); + Ok(AttributeStack::new( + gix_worktree::Stack::new( + // this is alright as we don't cause mutation of that directory, it's virtual. + self.work_dir().unwrap_or(self.git_dir()), + state, + case, + buf, + attribute_list, + ), + self, + )) + } + + /// Configure a file-system cache checking if files below the repository are excluded, reading `.gitignore` files from + /// the specified `source`. + /// + /// Note that no worktree is required for this to work, even though access to in-tree `.gitignore` files would require + /// a non-empty `index` that represents a tree with `.gitignore` files. + /// + /// This takes into consideration all the usual repository configuration, namely: + /// + /// * `$XDG_CONFIG_HOME/…/ignore` if `core.excludesFile` is *not* set, otherwise use the configured file. + /// * `$GIT_DIR/info/exclude` if present. + /// + /// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use + /// [`Repository::attributes()`] for accessing both attributes and excludes. + // TODO: test + #[cfg(feature = "excludes")] + pub fn excludes( + &self, + index: &gix_index::State, + overrides: Option<gix_ignore::Search>, + source: gix_worktree::stack::state::ignore::Source, + ) -> Result<AttributeStack<'_>, config::exclude_stack::Error> { + let case = if self.config.ignore_case { + gix_glob::pattern::Case::Fold + } else { + gix_glob::pattern::Case::Sensitive + }; + let mut buf = Vec::with_capacity(512); + let ignore = self + .config + .assemble_exclude_globals(self.git_dir(), overrides, source, &mut buf)?; + let state = gix_worktree::stack::State::IgnoreStack(ignore); + let attribute_list = state.id_mappings_from_index(index, index.path_backing(), case); + Ok(AttributeStack::new( + gix_worktree::Stack::new( + // this is alright as we don't cause mutation of that directory, it's virtual. + self.work_dir().unwrap_or(self.git_dir()), + state, + case, + buf, + attribute_list, + ), + self, )) } } diff --git a/vendor/gix/src/repository/config/mod.rs b/vendor/gix/src/repository/config/mod.rs index e5c8b64f3..618ccf0f6 100644 --- a/vendor/gix/src/repository/config/mod.rs +++ b/vendor/gix/src/repository/config/mod.rs @@ -22,6 +22,21 @@ impl crate::Repository { } } + /// Return filesystem options as retrieved from the repository configuration. + /// + /// Note that these values have not been [probed](gix_fs::Capabilities::probe()). + pub fn filesystem_options(&self) -> Result<gix_fs::Capabilities, config::boolean::Error> { + self.config.fs_capabilities() + } + + /// Return filesystem options on how to perform stat-checks, typically in relation to the index. + /// + /// Note that these values have not been [probed](gix_fs::Capabilities::probe()). + #[cfg(feature = "index")] + pub fn stat_options(&self) -> Result<gix_index::entry::stat::Options, config::stat_options::Error> { + self.config.stat_options() + } + /// The options used to open the repository. pub fn open_options(&self) -> &crate::open::Options { &self.options diff --git a/vendor/gix/src/repository/config/transport.rs b/vendor/gix/src/repository/config/transport.rs index dcfbc0bf6..99b5a7f47 100644 --- a/vendor/gix/src/repository/config/transport.rs +++ b/vendor/gix/src/repository/config/transport.rs @@ -1,6 +1,8 @@ #![allow(clippy::result_large_err)] use std::any::Any; +use gix_macros::momo; + use crate::bstr::BStr; impl crate::Repository { @@ -21,6 +23,7 @@ impl crate::Repository { )), allow(unused_variables) )] + #[momo] pub fn transport_options<'a>( &self, url: impl Into<&'a BStr>, @@ -359,7 +362,7 @@ impl crate::Repository { self.install_dir().ok().as_deref(), self.config.home_dir().as_deref(), )) - .map(|cow| cow.into_owned()) + .map(std::borrow::Cow::into_owned) }) .transpose() .with_leniency(lenient) diff --git a/vendor/gix/src/repository/excludes.rs b/vendor/gix/src/repository/excludes.rs deleted file mode 100644 index 6281549e0..000000000 --- a/vendor/gix/src/repository/excludes.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! exclude information -use crate::{config, Repository}; -impl Repository { - /// Configure a file-system cache checking if files below the repository are excluded, reading `.gitignore` files from - /// the specified `source`. - /// - /// Note that no worktree is required for this to work, even though access to in-tree `.gitignore` files would require - /// a non-empty `index` that represents a tree with `.gitignore` files. - /// - /// This takes into consideration all the usual repository configuration, namely: - /// - /// * `$XDG_CONFIG_HOME/…/ignore` if `core.excludesFile` is *not* set, otherwise use the configured file. - /// * `$GIT_DIR/info/exclude` if present. - /// - /// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use - /// [`Repository::attributes()`] for accessing both attributes and excludes. - // TODO: test, provide higher-level custom Cache wrapper that is much easier to use and doesn't panic when accessing entries - // by non-relative path. - pub fn excludes( - &self, - index: &gix_index::State, - overrides: Option<gix_ignore::Search>, - source: gix_worktree::cache::state::ignore::Source, - ) -> Result<gix_worktree::Cache, config::exclude_stack::Error> { - let case = if self.config.ignore_case { - gix_glob::pattern::Case::Fold - } else { - gix_glob::pattern::Case::Sensitive - }; - let mut buf = Vec::with_capacity(512); - let ignore = self - .config - .assemble_exclude_globals(self.git_dir(), overrides, source, &mut buf)?; - let state = gix_worktree::cache::State::IgnoreStack(ignore); - let attribute_list = state.id_mappings_from_index(index, index.path_backing(), source, case); - Ok(gix_worktree::Cache::new( - // this is alright as we don't cause mutation of that directory, it's virtual. - self.work_dir().unwrap_or(self.git_dir()), - state, - case, - buf, - attribute_list, - )) - } -} diff --git a/vendor/gix/src/repository/filter.rs b/vendor/gix/src/repository/filter.rs new file mode 100644 index 000000000..3aacb1a3d --- /dev/null +++ b/vendor/gix/src/repository/filter.rs @@ -0,0 +1,64 @@ +use crate::{filter, repository::IndexPersistedOrInMemory, Id, Repository}; + +/// +pub mod pipeline { + /// The error returned by [Repository::filter_pipeline()][super::Repository::filter_pipeline()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Could not obtain head commit of bare repository")] + HeadCommit(#[from] crate::reference::head_commit::Error), + #[error(transparent)] + DecodeCommit(#[from] gix_object::decode::Error), + #[error("Could not create index from tree at HEAD^{{tree}}")] + TreeTraverse(#[from] gix_traverse::tree::breadthfirst::Error), + #[error(transparent)] + BareAttributes(#[from] crate::config::attribute_stack::Error), + #[error(transparent)] + WorktreeIndex(#[from] crate::worktree::open_index::Error), + #[error(transparent)] + Init(#[from] crate::filter::pipeline::options::Error), + } +} + +impl Repository { + /// Configure a pipeline for converting byte buffers to the worktree representation, and byte streams to the git-internal + /// representation. Also return the index that was used when initializing the pipeline as it may be useful when calling + /// [convert_to_git()][filter::Pipeline::convert_to_git()]. + /// Bare repositories will either use `HEAD^{tree}` for accessing all relevant worktree files or the given `tree_if_bare`. + /// + /// Note that this is considered a primitive as it operates on data directly and will not have permanent effects. + /// We also return the index that was used to configure the attributes cache (for accessing `.gitattributes`), which can be reused + /// after it was possibly created from a tree, an expensive operation. + /// + /// ### Performance + /// + /// Note that when in a repository with worktree, files in the worktree will be read with priority, which causes at least a stat + /// each time the directory is changed. This can be expensive if access isn't in sorted order, which would cause more then necessary + /// stats: one per directory. + pub fn filter_pipeline( + &self, + tree_if_bare: Option<gix_hash::ObjectId>, + ) -> Result<(filter::Pipeline<'_>, IndexPersistedOrInMemory), pipeline::Error> { + let (cache, index) = if self.is_bare() { + let index = self.index_from_tree(&tree_if_bare.map_or_else( + || { + self.head_commit() + .map_err(pipeline::Error::from) + .and_then(|c| c.tree_id().map(Id::detach).map_err(Into::into)) + }, + Ok, + )?)?; + let cache = self.attributes_only(&index, gix_worktree::stack::state::attributes::Source::IdMapping)?; + (cache, IndexPersistedOrInMemory::InMemory(index)) + } else { + let index = self.index()?; + let cache = self.attributes_only( + &index, + gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping, + )?; + (cache, IndexPersistedOrInMemory::Persisted(index)) + }; + Ok((filter::Pipeline::new(self, cache.detach())?, index)) + } +} diff --git a/vendor/gix/src/repository/graph.rs b/vendor/gix/src/repository/graph.rs index a1f6c7f89..f4f2b18cc 100644 --- a/vendor/gix/src/repository/graph.rs +++ b/vendor/gix/src/repository/graph.rs @@ -7,18 +7,34 @@ impl crate::Repository { /// Note that the commitgraph will be used if it is present and readable, but it won't be an error if it is corrupted. In that case, /// it will just not be used. /// + /// Note that a commitgraph is only allowed to be used if `core.commitGraph` is true (the default), and that configuration errors are + /// ignored as well. + /// /// ### Performance /// - /// Note that the [Graph][gix_revision::Graph] can be sensitive to various object database settings that may affect the performance + /// Note that the [Graph][gix_revwalk::Graph] can be sensitive to various object database settings that may affect the performance /// of the commit walk. - pub fn commit_graph<T>(&self) -> gix_revision::Graph<'_, T> { - gix_revision::Graph::new( + pub fn revision_graph<T>(&self) -> gix_revwalk::Graph<'_, T> { + gix_revwalk::Graph::new( |id, buf| { self.objects .try_find(id, buf) - .map(|r| r.and_then(|d| d.try_into_commit_iter())) + .map(|r| r.and_then(gix_object::Data::try_into_commit_iter)) }, - gix_commitgraph::at(self.objects.store_ref().path().join("info")).ok(), + self.config + .may_use_commit_graph() + .unwrap_or(true) + .then(|| gix_commitgraph::at(self.objects.store_ref().path().join("info")).ok()) + .flatten(), ) } + + /// Return a cache for commits and their graph structure, as managed by `git commit-graph`, for accelerating commit walks on + /// a low level. + /// + /// Note that [`revision_graph()`][crate::Repository::revision_graph()] should be preferred for general purpose walks that don't + /// rely on the actual commit cache to be present, while leveraging it if possible. + pub fn commit_graph(&self) -> Result<gix_commitgraph::Graph, gix_commitgraph::init::Error> { + gix_commitgraph::at(self.objects.store_ref().path().join("info")) + } } diff --git a/vendor/gix/src/repository/identity.rs b/vendor/gix/src/repository/identity.rs index 61a4b4a98..a4e39089e 100644 --- a/vendor/gix/src/repository/identity.rs +++ b/vendor/gix/src/repository/identity.rs @@ -31,13 +31,13 @@ impl crate::Repository { 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())?, + name: p.committer.name.as_ref().or(p.user.name.as_ref()).map(AsRef::as_ref)?, email: p .committer .email .as_ref() .or(p.user.email.as_ref()) - .map(|v| v.as_ref())?, + .map(AsRef::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)), @@ -61,8 +61,8 @@ impl crate::Repository { 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())?, + name: p.author.name.as_ref().or(p.user.name.as_ref()).map(AsRef::as_ref)?, + email: p.author.email.as_ref().or(p.user.email.as_ref()).map(AsRef::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)), @@ -73,9 +73,9 @@ impl crate::Repository { } fn extract_time_or_default( - time: Option<&Result<gix_actor::Time, gix_date::parse::Error>>, + time: Option<&Result<gix_date::Time, gix_date::parse::Error>>, config_key: &'static keys::Time, -) -> Result<gix_actor::Time, config::time::Error> { +) -> Result<gix_date::Time, config::time::Error> { match time { Some(Ok(t)) => Ok(*t), None => Ok(gix_date::Time::now_local_or_utc()), @@ -88,7 +88,7 @@ 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>>, + pub time: Option<Result<gix_date::Time, gix_date::parse::Error>>, } #[derive(Debug, Clone)] @@ -117,11 +117,11 @@ impl Personas { 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()), + .map(std::borrow::Cow::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()), + .map(std::borrow::Cow::into_owned), ) } let now = SystemTime::now(); @@ -152,7 +152,7 @@ impl Personas { user_email = user_email.or_else(|| { config .string_by_key(gitoxide::User::EMAIL_FALLBACK.logical_name().as_str()) - .map(|v| v.into_owned()) + .map(std::borrow::Cow::into_owned) }); Personas { user: Entity { diff --git a/vendor/gix/src/repository/impls.rs b/vendor/gix/src/repository/impls.rs index 5da55290c..36fd788dc 100644 --- a/vendor/gix/src/repository/impls.rs +++ b/vendor/gix/src/repository/impls.rs @@ -7,8 +7,11 @@ impl Clone for crate::Repository { self.common_dir.clone(), self.config.clone(), self.options.clone(), + #[cfg(feature = "index")] self.index.clone(), self.shallow_commits.clone(), + #[cfg(feature = "attributes")] + self.modules.clone(), ) } } @@ -40,8 +43,11 @@ impl From<&crate::ThreadSafeRepository> for crate::Repository { repo.common_dir.clone(), repo.config.clone(), repo.linked_worktree_options.clone(), + #[cfg(feature = "index")] repo.index.clone(), repo.shallow_commits.clone(), + #[cfg(feature = "attributes")] + repo.modules.clone(), ) } } @@ -55,8 +61,11 @@ impl From<crate::ThreadSafeRepository> for crate::Repository { repo.common_dir, repo.config, repo.linked_worktree_options, + #[cfg(feature = "index")] repo.index, repo.shallow_commits, + #[cfg(feature = "attributes")] + repo.modules.clone(), ) } } @@ -70,7 +79,10 @@ impl From<crate::Repository> for crate::ThreadSafeRepository { common_dir: r.common_dir, config: r.config, linked_worktree_options: r.options, + #[cfg(feature = "index")] index: r.index, + #[cfg(feature = "attributes")] + modules: r.modules, shallow_commits: r.shallow_commits, } } diff --git a/vendor/gix/src/repository/index.rs b/vendor/gix/src/repository/index.rs new file mode 100644 index 000000000..a21b138a5 --- /dev/null +++ b/vendor/gix/src/repository/index.rs @@ -0,0 +1,133 @@ +use gix_odb::FindExt; + +use crate::{config::cache::util::ApplyLeniencyDefault, repository::IndexPersistedOrInMemory, worktree}; + +/// Index access +impl crate::Repository { + /// 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. + pub fn open_index(&self) -> Result<gix_index::File, worktree::open_index::Error> { + let thread_limit = self + .config + .resolved + .string("index", None, "threads") + .map(|value| crate::config::tree::Index::THREADS.try_into_index_threads(value)) + .transpose() + .with_lenient_default(self.config.lenient_config)?; + let skip_hash = self + .config + .resolved + .boolean("index", None, "skipHash") + .map(|res| crate::config::tree::Index::SKIP_HASH.enrich_error(res)) + .transpose() + .with_lenient_default(self.config.lenient_config)? + .unwrap_or_default(); + + let index = gix_index::File::at( + self.index_path(), + self.object_hash(), + skip_hash, + gix_index::decode::Options { + thread_limit, + min_extension_block_in_bytes_for_threading: 0, + expected_checksum: None, + }, + )?; + + Ok(index) + } + + /// 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.try_index().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()), + )), + )), + }) + } + + /// 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, or `None` if no such file exists. + /// + /// The index file is shared across all clones of this repository. + pub fn try_index(&self) -> Result<Option<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), + }) + }, + ) + } + + /// Open the persisted worktree index or generate it from the current `HEAD^{tree}` to live in-memory only. + /// + /// Use this method to get an index in any repository, even bare ones that don't have one naturally. + /// + /// ### Note + /// + /// The locally stored index is not guaranteed to represent `HEAD^{tree}` if this repository is bare - bare repos + /// don't naturally have an index and if an index is present it must have been generated by hand. + pub fn index_or_load_from_head( + &self, + ) -> Result<IndexPersistedOrInMemory, crate::repository::index_or_load_from_head::Error> { + Ok(match self.try_index()? { + Some(index) => IndexPersistedOrInMemory::Persisted(index), + None => { + let tree = self.head_commit()?.tree_id()?; + IndexPersistedOrInMemory::InMemory(self.index_from_tree(&tree)?) + } + }) + } + + /// Create new index-file, which would live at the correct location, in memory from the given `tree`. + /// + /// Note that this is an expensive operation as it requires recursively traversing the entire tree to unpack it into the index. + pub fn index_from_tree( + &self, + tree: &gix_hash::oid, + ) -> Result<gix_index::File, gix_traverse::tree::breadthfirst::Error> { + Ok(gix_index::File::from_state( + gix_index::State::from_tree(tree, |oid, buf| self.objects.find_tree_iter(oid, buf).ok())?, + self.git_dir().join("index"), + )) + } +} + +impl std::ops::Deref for IndexPersistedOrInMemory { + type Target = gix_index::File; + + fn deref(&self) -> &Self::Target { + match self { + IndexPersistedOrInMemory::Persisted(i) => i, + IndexPersistedOrInMemory::InMemory(i) => i, + } + } +} + +impl IndexPersistedOrInMemory { + /// Consume this instance and turn it into an owned index file. + /// + /// Note that this will cause the persisted index to be cloned, which would happen whenever the repository has a worktree. + pub fn into_owned(self) -> gix_index::File { + match self { + IndexPersistedOrInMemory::Persisted(i) => gix_index::File::clone(&i), + IndexPersistedOrInMemory::InMemory(i) => i, + } + } +} diff --git a/vendor/gix/src/repository/init.rs b/vendor/gix/src/repository/init.rs index 255ff90d6..65b3d59ab 100644 --- a/vendor/gix/src/repository/init.rs +++ b/vendor/gix/src/repository/init.rs @@ -4,15 +4,16 @@ impl crate::Repository { #[allow(clippy::too_many_arguments)] pub(crate) fn from_refs_and_objects( refs: crate::RefStore, - objects: crate::OdbHandle, + mut 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, + #[cfg(feature = "index")] index: crate::worktree::IndexStorage, shallow_commits: crate::shallow::CommitsStorage, + #[cfg(feature = "attributes")] modules: crate::submodule::ModulesFileStorage, ) -> Self { - let objects = setup_objects(objects, &config); + setup_objects(&mut objects, &config); crate::Repository { bufs: RefCell::new(Vec::with_capacity(4)), work_tree, @@ -21,8 +22,11 @@ impl crate::Repository { refs, config, options: linked_worktree_options, + #[cfg(feature = "index")] index, shallow_commits, + #[cfg(feature = "attributes")] + modules, } } @@ -33,7 +37,7 @@ impl crate::Repository { } #[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 { +pub(crate) fn setup_objects(objects: &mut crate::OdbHandle, config: &crate::config::Cache) { #[cfg(feature = "max-performance-safe")] { match config.pack_cache_bytes { @@ -54,10 +58,5 @@ fn setup_objects(mut objects: crate::OdbHandle, config: &crate::config::Cache) - 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 index 3e2ff907c..5811e7bf9 100644 --- a/vendor/gix/src/repository/location.rs +++ b/vendor/gix/src/repository/location.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use gix_path::realpath::MAX_SYMLINKS; @@ -25,6 +25,12 @@ impl crate::Repository { self.git_dir().join("index") } + /// The path to the `.gitmodules` file in the worktree, if a worktree is available. + #[cfg(feature = "attributes")] + pub fn modules_path(&self) -> Option<PathBuf> { + self.work_dir().map(|wtd| wtd.join(crate::submodule::MODULES_FILE)) + } + /// 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() @@ -42,30 +48,18 @@ impl crate::Repository { } /// 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. + /// Note that it may be `None` if there is no work tree, or if CWD isn't inside of the working tree directory. + /// + /// Note that the CWD is obtained once upon instantiation of the repository. // 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) - }) - }) - }) + pub fn prefix(&self) -> Result<Option<&Path>, gix_path::realpath::Error> { + let (root, current_dir) = match self.work_dir().zip(self.options.current_dir.as_deref()) { + Some((work_dir, cwd)) => (work_dir, cwd), + None => return Ok(None), + }; + + let root = gix_path::realpath_opts(root, current_dir, MAX_SYMLINKS)?; + Ok(current_dir.strip_prefix(&root).ok()) } /// Return the kind of repository, either bare or one with a work tree. diff --git a/vendor/gix/src/repository/snapshots.rs b/vendor/gix/src/repository/mailmap.rs index 96de5080d..b4a2f4a0e 100644 --- a/vendor/gix/src/repository/snapshots.rs +++ b/vendor/gix/src/repository/mailmap.rs @@ -37,12 +37,11 @@ impl crate::Repository { }); 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()) + tree.find_entry(".mailmap").map(|e| e.object_id()) }) }); } diff --git a/vendor/gix/src/repository/mod.rs b/vendor/gix/src/repository/mod.rs index f8a51e8d0..e3742894b 100644 --- a/vendor/gix/src/repository/mod.rs +++ b/vendor/gix/src/repository/mod.rs @@ -4,6 +4,8 @@ #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Kind { /// A submodule worktree, whose `git` repository lives in `.git/modules/**/<name>` of the parent repository. + /// + /// Note that 'old-form' submodule will register as `Worktree {is_linked: false}`. Submodule, /// A bare repository does not have a work tree, that is files on disk beyond the `git` repository itself. Bare, @@ -33,22 +35,106 @@ impl crate::Repository { } } -mod attributes; +#[cfg(any(feature = "attributes", feature = "excludes"))] +pub mod attributes; mod cache; mod config; -mod excludes; +/// +#[cfg(feature = "attributes")] +pub mod filter; mod graph; pub(crate) mod identity; mod impls; -mod init; +#[cfg(feature = "index")] +mod index; +pub(crate) mod init; mod kind; mod location; +#[cfg(feature = "mailmap")] +mod mailmap; mod object; +#[cfg(feature = "attributes")] +mod pathspec; mod reference; mod remote; +#[cfg(feature = "revision")] mod revision; mod shallow; -mod snapshots; mod state; +#[cfg(feature = "attributes")] +mod submodule; mod thread_safe; mod worktree; + +/// A type to represent an index which either was loaded from disk as it was persisted there, or created on the fly in memory. +#[cfg(feature = "index")] +pub enum IndexPersistedOrInMemory { + /// The index as loaded from disk, and shared across clones of the owning `Repository`. + Persisted(crate::worktree::Index), + /// A temporary index as created from the `HEAD^{tree}`, with the file path set to the place where it would be stored naturally. + /// + /// Note that unless saved explicitly, it will not persist. + InMemory(gix_index::File), +} + +/// +#[cfg(feature = "attributes")] +pub mod pathspec_defaults_ignore_case { + /// The error returned by [Repository::pathspec_defaults_ignore_case()](crate::Repository::pathspec_defaults_inherit_ignore_case()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Filesystem configuration could not be obtained to learn about case sensitivity")] + FilesystemConfig(#[from] crate::config::boolean::Error), + #[error(transparent)] + Defaults(#[from] gix_pathspec::defaults::from_environment::Error), + } +} + +/// +#[cfg(feature = "index")] +pub mod index_or_load_from_head { + /// The error returned by [`Repository::index_or_load_from_head()`][crate::Repository::index_or_load_from_head()]. + #[derive(thiserror::Error, Debug)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + HeadCommit(#[from] crate::reference::head_commit::Error), + #[error(transparent)] + TreeId(#[from] gix_object::decode::Error), + #[error(transparent)] + TraverseTree(#[from] gix_traverse::tree::breadthfirst::Error), + #[error(transparent)] + OpenIndex(#[from] crate::worktree::open_index::Error), + } +} + +/// +#[cfg(feature = "worktree-stream")] +pub mod worktree_stream { + /// The error returned by [`Repository::worktree_stream()`][crate::Repository::worktree_stream()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + FindTree(#[from] crate::object::find::existing::Error), + #[error(transparent)] + OpenTree(#[from] gix_traverse::tree::breadthfirst::Error), + #[error(transparent)] + AttributesCache(#[from] crate::config::attribute_stack::Error), + #[error(transparent)] + FilterPipeline(#[from] crate::filter::pipeline::options::Error), + #[error("Needed {id} to be a tree to turn into a workspace stream, got {actual}")] + NotATree { + id: gix_hash::ObjectId, + actual: gix_object::Kind, + }, + } +} + +/// +#[cfg(feature = "worktree-archive")] +pub mod worktree_archive { + /// The error returned by [`Repository::worktree_archive()`][crate::Repository::worktree_archive()]. + pub type Error = gix_archive::Error; +} diff --git a/vendor/gix/src/repository/object.rs b/vendor/gix/src/repository/object.rs index 787dcda4e..c156971d0 100644 --- a/vendor/gix/src/repository/object.rs +++ b/vendor/gix/src/repository/object.rs @@ -2,11 +2,13 @@ use std::{convert::TryInto, ops::DerefMut}; use gix_hash::ObjectId; -use gix_odb::{Find, FindExt, Write}; +use gix_macros::momo; +use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; use gix_ref::{ transaction::{LogChange, PreviousValue, RefLog}, FullName, }; +use smallvec::SmallVec; use crate::{commit, ext::ObjectIdExt, object, tag, Id, Object, Reference, Tree}; @@ -21,6 +23,7 @@ impl crate::Repository { /// /// 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. + #[momo] 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()) { @@ -32,11 +35,46 @@ impl crate::Repository { }); } let mut buf = self.free_buf(); - let kind = self.objects.find(id, &mut buf)?.kind; + let kind = self.objects.find(&id, &mut buf)?.kind; Ok(Object::from_data(id, kind, buf, self)) } + /// Obtain information about an object without fully decoding it, or fail if the object doesn't exist. + /// + /// Note that despite being cheaper than [`Self::find_object()`], there is still some effort traversing delta-chains. + #[doc(alias = "read_header", alias = "git2")] + #[momo] + pub fn find_header(&self, id: impl Into<ObjectId>) -> Result<gix_odb::find::Header, object::find::existing::Error> { + let id = id.into(); + if id == gix_hash::ObjectId::empty_tree(self.object_hash()) { + return Ok(gix_odb::find::Header::Loose { + kind: gix_object::Kind::Tree, + size: 0, + }); + } + self.objects.header(id) + } + + /// Obtain information about an object without fully decoding it, or `None` if the object doesn't exist. + /// + /// Note that despite being cheaper than [`Self::try_find_object()`], there is still some effort traversing delta-chains. + #[momo] + pub fn try_find_header( + &self, + id: impl Into<ObjectId>, + ) -> Result<Option<gix_odb::find::Header>, object::find::Error> { + let id = id.into(); + if id == gix_hash::ObjectId::empty_tree(self.object_hash()) { + return Ok(Some(gix_odb::find::Header::Loose { + kind: gix_object::Kind::Tree, + size: 0, + })); + } + self.objects.try_header(&id).map_err(Into::into) + } + /// Try to find the object with `id` or return `None` if it wasn't found. + #[momo] 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()) { @@ -49,7 +87,7 @@ impl crate::Repository { } let mut buf = self.free_buf(); - match self.objects.try_find(id, &mut buf)? { + match self.objects.try_find(&id, &mut buf)? { Some(obj) => { let kind = obj.kind; Ok(Some(Object::from_data(id, kind, buf, self))) @@ -76,15 +114,19 @@ impl crate::Repository { /// we avoid writing duplicate objects using slow disks that will eventually have to be garbage collected. pub fn write_object(&self, object: impl gix_object::WriteTo) -> Result<Id<'_>, object::write::Error> { let mut buf = self.shared_empty_buf(); - object.write_to(buf.deref_mut())?; + object.write_to(buf.deref_mut()).expect("write to memory works"); - let oid = gix_object::compute_hash(self.object_hash(), object.kind(), &buf); - if self.objects.contains(oid) { + self.write_object_inner(&buf, object.kind()) + } + + fn write_object_inner(&self, buf: &[u8], kind: gix_object::Kind) -> Result<Id<'_>, object::write::Error> { + let oid = gix_object::compute_hash(self.object_hash(), kind, buf); + if self.objects.contains(&oid) { return Ok(oid.attach(self)); } self.objects - .write_buf(object.kind(), &buf) + .write_buf(kind, buf) .map(|oid| oid.attach(self)) .map_err(Into::into) } @@ -93,14 +135,16 @@ impl crate::Repository { /// /// We avoid writing duplicate objects to slow disks that will eventually have to be garbage collected by /// pre-hashing the data, and checking if the object is already present. + #[momo] pub fn write_blob(&self, bytes: impl AsRef<[u8]>) -> Result<Id<'_>, object::write::Error> { let bytes = bytes.as_ref(); let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, bytes); - if self.objects.contains(oid) { + if self.objects.contains(&oid) { return Ok(oid.attach(self)); } self.objects .write_buf(gix_object::Kind::Blob, bytes) + .map_err(Into::into) .map(|oid| oid.attach(self)) } @@ -115,14 +159,20 @@ impl crate::Repository { mut bytes: impl std::io::Read + std::io::Seek, ) -> Result<Id<'_>, object::write::Error> { let mut buf = self.shared_empty_buf(); - std::io::copy(&mut bytes, buf.deref_mut())?; - let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, &buf); - if self.objects.contains(oid) { + std::io::copy(&mut bytes, buf.deref_mut()).expect("write to memory works"); + + self.write_blob_stream_inner(&buf) + } + + fn write_blob_stream_inner(&self, buf: &[u8]) -> Result<Id<'_>, object::write::Error> { + let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, buf); + if self.objects.contains(&oid) { return Ok(oid.attach(self)); } self.objects - .write_buf(gix_object::Kind::Blob, &buf) + .write_buf(gix_object::Kind::Blob, buf) + .map_err(Into::into) .map(|oid| oid.attach(self)) } @@ -131,6 +181,7 @@ impl crate::Repository { /// /// 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). + #[momo] pub fn tag( &self, name: impl AsRef<str>, @@ -168,6 +219,25 @@ impl crate::Repository { Name: TryInto<FullName, Error = E>, commit::Error: From<E>, { + self.commit_as_inner( + committer.into(), + author.into(), + reference.try_into()?, + message.as_ref(), + tree.into(), + parents.into_iter().map(Into::into).collect(), + ) + } + + fn commit_as_inner( + &self, + committer: gix_actor::SignatureRef<'_>, + author: gix_actor::SignatureRef<'_>, + reference: FullName, + message: &str, + tree: ObjectId, + parents: SmallVec<[gix_hash::ObjectId; 1]>, + ) -> Result<Id<'_>, commit::Error> { use gix_ref::{ transaction::{Change, RefEdit}, Target, @@ -175,14 +245,13 @@ impl crate::Repository { // 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(), + message: message.into(), + tree, + author: author.into(), + committer: committer.into(), encoding: None, - parents: parents.into_iter().map(|id| id.into()).collect(), + parents, extra_headers: Default::default(), }; diff --git a/vendor/gix/src/repository/pathspec.rs b/vendor/gix/src/repository/pathspec.rs new file mode 100644 index 000000000..8e7e9bbe9 --- /dev/null +++ b/vendor/gix/src/repository/pathspec.rs @@ -0,0 +1,55 @@ +use gix_pathspec::MagicSignature; + +use crate::{bstr::BStr, config::cache::util::ApplyLeniencyDefault, AttributeStack, Pathspec, Repository}; + +impl Repository { + /// Create a new pathspec abstraction that allows to conduct searches using `patterns`. + /// `inherit_ignore_case` should be `true` if `patterns` will match against files on disk, or `false` otherwise, for more natural matching + /// (but also note that `git` does not do that). + /// `index` may be needed to load attributes which is required only if `patterns` refer to attributes via `:(attr:…)` syntax. + /// In the same vein, `attributes_source` affects where `.gitattributes` files are read from if pathspecs need to match against attributes. + /// + /// It will be initialized exactly how it would, and attribute matching will be conducted by reading the worktree first if available. + /// If that is not desirable, consider calling [`Pathspec::new()`] directly. + #[doc(alias = "Pathspec", alias = "git2")] + pub fn pathspec( + &self, + patterns: impl IntoIterator<Item = impl AsRef<BStr>>, + inherit_ignore_case: bool, + index: &gix_index::State, + attributes_source: gix_worktree::stack::state::attributes::Source, + ) -> Result<Pathspec<'_>, crate::pathspec::init::Error> { + Pathspec::new(self, patterns, inherit_ignore_case, || { + self.attributes_only(index, attributes_source) + .map(AttributeStack::detach) + .map_err(Into::into) + }) + } + + /// Return default settings that are required when [parsing pathspecs](gix_pathspec::parse()) by hand. + /// + /// These are stemming from environment variables which have been converted to [config settings](crate::config::tree::gitoxide::Pathspec), + /// which now serve as authority for configuration. + pub fn pathspec_defaults(&self) -> Result<gix_pathspec::Defaults, gix_pathspec::defaults::from_environment::Error> { + self.config.pathspec_defaults() + } + + /// Similar to [Self::pathspec_defaults()], but will automatically configure the returned defaults to match case-insensitively if the underlying + /// filesystem is also configured to be case-insensitive according to `core.ignoreCase`, and `inherit_ignore_case` is `true`. + pub fn pathspec_defaults_inherit_ignore_case( + &self, + inherit_ignore_case: bool, + ) -> Result<gix_pathspec::Defaults, crate::repository::pathspec_defaults_ignore_case::Error> { + let mut defaults = self.config.pathspec_defaults()?; + if inherit_ignore_case + && self + .config + .fs_capabilities() + .with_lenient_default(self.config.lenient_config)? + .ignore_case + { + defaults.signature |= MagicSignature::ICASE; + } + Ok(defaults) + } +} diff --git a/vendor/gix/src/repository/reference.rs b/vendor/gix/src/repository/reference.rs index e5a8aadcb..5a14c60b5 100644 --- a/vendor/gix/src/repository/reference.rs +++ b/vendor/gix/src/repository/reference.rs @@ -1,6 +1,7 @@ use std::convert::TryInto; use gix_hash::ObjectId; +use gix_macros::momo; use gix_ref::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}, FullName, PartialNameRef, Target, @@ -14,6 +15,7 @@ impl crate::Repository { /// /// 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). + #[momo] pub fn tag_reference( &self, name: impl AsRef<str>, @@ -60,10 +62,10 @@ impl crate::Repository { pub fn set_namespace<'a, Name, E>( &mut self, namespace: Name, - ) -> Result<Option<gix_ref::Namespace>, gix_validate::refname::Error> + ) -> Result<Option<gix_ref::Namespace>, gix_validate::reference::name::Error> where Name: TryInto<&'a PartialNameRef, Error = E>, - gix_validate::refname::Error: From<E>, + gix_validate::reference::name::Error: From<E>, { let namespace = gix_ref::namespace::expand(namespace)?; Ok(self.refs.namespace.replace(namespace)) @@ -85,14 +87,27 @@ impl crate::Repository { 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(); + self.reference_inner( + name.try_into().map_err(gix_validate::reference::name::Error::from)?, + target.into(), + constraint, + log_message.into(), + ) + } + + fn reference_inner( + &self, + name: FullName, + id: ObjectId, + constraint: PreviousValue, + log_message: BString, + ) -> Result<Reference<'_>, reference::edit::Error> { let mut edits = self.edit_reference(RefEdit { change: Change::Update { log: LogChange { mode: RefLog::AndReference, force_create_reflog: false, - message: log_message.into(), + message: log_message, }, expected: constraint, new: Target::Peeled(id), @@ -124,7 +139,7 @@ impl crate::Repository { /// 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()]. + /// [overriding the git-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. @@ -180,7 +195,7 @@ impl crate::Repository { /// 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())) + Ok(self.head()?.referent_name().map(std::borrow::ToOwned::to_owned)) } /// Return the reference that `HEAD` points to, or `None` if the head is detached or unborn. diff --git a/vendor/gix/src/repository/remote.rs b/vendor/gix/src/repository/remote.rs index 74ebbaea0..be0845178 100644 --- a/vendor/gix/src/repository/remote.rs +++ b/vendor/gix/src/repository/remote.rs @@ -28,7 +28,8 @@ impl crate::Repository { 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()]. + /// Find the configured 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> { @@ -42,7 +43,7 @@ impl crate::Repository { /// 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. + /// See [`remote_default_name()`](Self::remote_default_name()) for more information on the `direction` parameter. pub fn find_default_remote( &self, direction: remote::Direction, @@ -51,8 +52,8 @@ impl crate::Repository { .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. + /// Find the configured remote with the given `name_or_url` or return `None` if it doesn't exist, + /// for the purpose of fetching or pushing data. /// /// 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. @@ -62,7 +63,36 @@ impl crate::Repository { /// /// 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) + self.try_find_remote_inner(name_or_url.into(), true) + } + + /// This method emulate what `git fetch <remote>` does in order to obtain a remote to fetch from. + /// + /// As such, with `name_or_url` being `Some`, it will: + /// + /// * use `name_or_url` verbatim if it is a URL, creating a new remote in memory as needed. + /// * find the named remote if `name_or_url` is a remote name + /// + /// If `name_or_url` is `None`: + /// + /// * use the current `HEAD` branch to find a configured remote + /// * fall back to either a generally configured remote or the only configured remote. + /// + /// Fail if no remote could be found despite all of the above. + pub fn find_fetch_remote(&self, name_or_url: Option<&BStr>) -> Result<Remote<'_>, find::for_fetch::Error> { + Ok(match name_or_url { + Some(name) => match self.try_find_remote(name).and_then(Result::ok) { + Some(remote) => remote, + None => self.remote_at(gix_url::parse(name)?)?, + }, + None => self + .head()? + .into_remote(remote::Direction::Fetch) + .transpose()? + .map(Ok) + .or_else(|| self.find_default_remote(remote::Direction::Fetch)) + .ok_or_else(|| find::for_fetch::Error::ExactlyOneRemoteNotAvailable)??, + }) } /// Similar to [`try_find_remote()`][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid @@ -72,7 +102,7 @@ impl crate::Repository { &self, name_or_url: impl Into<&'a BStr>, ) -> Option<Result<Remote<'_>, find::Error>> { - self.try_find_remote_inner(name_or_url, false) + self.try_find_remote_inner(name_or_url.into(), false) } fn try_find_remote_inner<'a>( diff --git a/vendor/gix/src/repository/revision.rs b/vendor/gix/src/repository/revision.rs index 3018c2be8..bb9b56d57 100644 --- a/vendor/gix/src/repository/revision.rs +++ b/vendor/gix/src/repository/revision.rs @@ -1,4 +1,5 @@ use crate::{bstr::BStr, revision, Id}; +use gix_macros::momo; /// Methods for resolving revisions by spec or working with the commit graph. impl crate::Repository { @@ -8,6 +9,8 @@ impl crate::Repository { /// /// - `@` actually stands for `HEAD`, whereas `git` resolves it to the object pointed to by `HEAD` without making the /// `HEAD` ref available for lookups. + #[doc(alias = "revparse", alias = "git2")] + #[momo] pub fn rev_parse<'a>(&self, spec: impl Into<&'a BStr>) -> Result<revision::Spec<'_>, revision::spec::parse::Error> { revision::Spec::from_bstr( spec, @@ -20,6 +23,7 @@ impl crate::Repository { } /// Parse a revision specification and return single object id as represented by this instance. + #[doc(alias = "revparse_single", alias = "git2")] pub fn rev_parse_single<'repo, 'a>( &'repo self, spec: impl Into<&'a BStr>, @@ -33,6 +37,7 @@ impl crate::Repository { /// 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. + #[doc(alias = "revwalk", alias = "git2")] pub fn rev_walk( &self, tips: impl IntoIterator<Item = impl Into<gix_hash::ObjectId>>, diff --git a/vendor/gix/src/repository/submodule.rs b/vendor/gix/src/repository/submodule.rs new file mode 100644 index 000000000..a605bfbd3 --- /dev/null +++ b/vendor/gix/src/repository/submodule.rs @@ -0,0 +1,96 @@ +use std::rc::Rc; + +use crate::{submodule, Repository}; + +impl Repository { + /// Open the `.gitmodules` file as present in the worktree, or return `None` if no such file is available. + /// Note that git configuration is also contributing to the result based on the current snapshot. + /// + /// Note that his method will not look in other places, like the index or the `HEAD` tree. + // TODO(submodule): make it use an updated snapshot instead once we have `config()`. + pub fn open_modules_file(&self) -> Result<Option<gix_submodule::File>, submodule::open_modules_file::Error> { + let path = match self.modules_path() { + Some(path) => path, + None => return Ok(None), + }; + let buf = match std::fs::read(&path) { + Ok(buf) => buf, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err.into()), + }; + + Ok(Some(gix_submodule::File::from_bytes( + &buf, + path, + &self.config.resolved, + )?)) + } + + /// Return a shared [`.gitmodules` file](crate::submodule::File) which is updated automatically if the in-memory snapshot + /// has become stale as the underlying file on disk has changed. The snapshot based on the file on disk is shared across all + /// clones of this repository. + /// + /// If a file on disk isn't present, we will try to load it from the index, and finally from the current tree. + /// In the latter two cases, the result will not be cached in this repository instance as we can't detect freshness anymore, + /// so time this method is called a new [modules file](submodule::ModulesSnapshot) will be created. + /// + /// Note that git configuration is also contributing to the result based on the current snapshot. + /// + // TODO(submodule): make it use an updated snapshot instead once we have `config()`. + pub fn modules(&self) -> Result<Option<submodule::ModulesSnapshot>, submodule::modules::Error> { + match self.modules.recent_snapshot( + || { + self.modules_path() + .and_then(|path| path.metadata().and_then(|m| m.modified()).ok()) + }, + || self.open_modules_file(), + )? { + Some(m) => Ok(Some(m)), + None => { + let id = match self.try_index()?.and_then(|index| { + index + .entry_by_path(submodule::MODULES_FILE.into()) + .map(|entry| entry.id) + }) { + Some(id) => id, + None => match self + .head_commit()? + .tree()? + .find_entry(submodule::MODULES_FILE) + .map(|entry| entry.inner.oid) + { + Some(id) => id.to_owned(), + None => return Ok(None), + }, + }; + Ok(Some(gix_features::threading::OwnShared::new( + gix_submodule::File::from_bytes(&self.find_object(id)?.data, None, &self.config.resolved) + .map_err(submodule::open_modules_file::Error::from)? + .into(), + ))) + } + } + } + + /// Return the list of available submodules, or `None` if there is no submodule configuration. + #[doc(alias = "git2")] + pub fn submodules(&self) -> Result<Option<impl Iterator<Item = crate::Submodule<'_>>>, submodule::modules::Error> { + let modules = match self.modules()? { + None => return Ok(None), + Some(m) => m, + }; + let shared_state = Rc::new(submodule::SharedState::new(self, modules)); + Ok(Some( + shared_state + .modules + .names() + .map(ToOwned::to_owned) + .collect::<Vec<_>>() + .into_iter() + .map(move |name| crate::Submodule { + state: shared_state.clone(), + name, + }), + )) + } +} diff --git a/vendor/gix/src/repository/worktree.rs b/vendor/gix/src/repository/worktree.rs index f522a3f18..cc6f0bf73 100644 --- a/vendor/gix/src/repository/worktree.rs +++ b/vendor/gix/src/repository/worktree.rs @@ -1,4 +1,4 @@ -use crate::{config::cache::util::ApplyLeniencyDefault, worktree, Worktree}; +use crate::{worktree, Worktree}; /// Interact with individual worktrees and their information. impl crate::Repository { @@ -37,7 +37,8 @@ impl crate::Repository { /// 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. + /// registered worktree in the current working dir, even if no `.git` file or directory exists. + /// It's merely based on configuration, see [Worktree::dot_git_exists()] for a way to perform more validation. pub fn worktree(&self) -> Option<Worktree<'_>> { self.work_dir().map(|path| Worktree { parent: self, path }) } @@ -50,57 +51,95 @@ impl crate::Repository { self.config.is_bare && self.work_dir().is_none() } - /// Open a new copy of the index file and decode it entirely. + /// If `id` points to a tree, produce a stream that yields one worktree entry after the other. The index of the tree at `id` + /// is returned as well as it is an intermediate byproduct that might be useful to callers. /// - /// 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. - pub fn open_index(&self) -> Result<gix_index::File, worktree::open_index::Error> { - let thread_limit = self - .config - .resolved - .string("index", None, "threads") - .map(|value| crate::config::tree::Index::THREADS.try_into_index_threads(value)) - .transpose() - .with_lenient_default(self.config.lenient_config)?; - 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, + /// The entries will look exactly like they would if one would check them out, with filters applied. + /// The `export-ignore` attribute is used to skip blobs or directories to which it applies. + #[cfg(feature = "worktree-stream")] + #[gix_macros::momo] + pub fn worktree_stream( + &self, + id: impl Into<gix_hash::ObjectId>, + ) -> Result<(gix_worktree_stream::Stream, gix_index::File), crate::repository::worktree_stream::Error> { + use gix_odb::{FindExt, HeaderExt}; + let id = id.into(); + let header = self.objects.header(id)?; + if !header.kind().is_tree() { + return Err(crate::repository::worktree_stream::Error::NotATree { + id, + actual: header.kind(), + }); + } + + // TODO(perf): potential performance improvements could be to use the index at `HEAD` if possible (`index_from_head_tree…()`) + // TODO(perf): when loading a non-HEAD tree, we effectively traverse the tree twice. This is usually fast though, and sharing + // an object cache between the copies of the ODB handles isn't trivial and needs a lock. + let index = self.index_from_tree(&id)?; + let mut cache = self + .attributes_only(&index, gix_worktree::stack::state::attributes::Source::IdMapping)? + .detach(); + let pipeline = + gix_filter::Pipeline::new(cache.attributes_collection(), crate::filter::Pipeline::options(self)?); + let objects = self.objects.clone().into_arc().expect("TBD error handling"); + let stream = gix_worktree_stream::from_tree( + id, + { + let objects = objects.clone(); + move |id, buf| objects.find(id, buf) + }, + pipeline, + move |path, mode, attrs| -> std::io::Result<()> { + let entry = cache.at_entry(path, Some(mode.is_tree()), |id, buf| objects.find_blob(id, buf))?; + entry.matching_attributes(attrs); + Ok(()) }, - ) - .map_err(Into::into) + ); + Ok((stream, index)) } - /// 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. + /// Produce an archive from the `stream` and write it to `out` according to `options`. + /// Use `blob` to provide progress for each entry written to `out`, and note that it should already be initialized to the amount + /// of expected entries, with `should_interrupt` being queried between each entry to abort if needed, and on each write to `out`. + /// + /// ### Performance + /// + /// Be sure that `out` is able to handle a lot of write calls. Otherwise wrap it in a [`BufWriter`][std::io::BufWriter]. /// - /// 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()), - )), - )), - }) + /// ### Additional progress and fine-grained interrupt handling + /// + /// For additional progress reporting, wrap `out` into a writer that counts throughput on each write. + /// This can also be used to react to interrupts on each write, instead of only for each entry. + #[cfg(feature = "worktree-archive")] + pub fn worktree_archive( + &self, + mut stream: gix_worktree_stream::Stream, + out: impl std::io::Write + std::io::Seek, + blobs: impl gix_features::progress::Count, + should_interrupt: &std::sync::atomic::AtomicBool, + options: gix_archive::Options, + ) -> Result<(), crate::repository::worktree_archive::Error> { + let mut out = gix_features::interrupt::Write { + inner: out, + should_interrupt, + }; + if options.format == gix_archive::Format::InternalTransientNonPersistable { + std::io::copy(&mut stream.into_read(), &mut out)?; + return Ok(()); + } + gix_archive::write_stream_seek( + &mut stream, + |stream| { + if should_interrupt.load(std::sync::atomic::Ordering::Relaxed) { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Cancelled by user").into()); + } + let res = stream.next_entry(); + blobs.inc(); + res + }, + out, + options, + )?; + Ok(()) } } |