diff options
Diffstat (limited to 'vendor/gix/src/submodule')
-rw-r--r-- | vendor/gix/src/submodule/errors.rs | 106 | ||||
-rw-r--r-- | vendor/gix/src/submodule/mod.rs | 287 |
2 files changed, 393 insertions, 0 deletions
diff --git a/vendor/gix/src/submodule/errors.rs b/vendor/gix/src/submodule/errors.rs new file mode 100644 index 000000000..4e41337de --- /dev/null +++ b/vendor/gix/src/submodule/errors.rs @@ -0,0 +1,106 @@ +/// +pub mod open_modules_file { + /// The error returned by [Repository::open_modules_file()](crate::Repository::open_modules_file()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Configuration(#[from] gix_config::parse::Error), + #[error("Could not read '.gitmodules' file")] + Io(#[from] std::io::Error), + } +} + +/// +pub mod modules { + /// The error returned by [Repository::modules()](crate::Repository::modules()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + OpenModulesFile(#[from] crate::submodule::open_modules_file::Error), + #[error(transparent)] + OpenIndex(#[from] crate::worktree::open_index::Error), + #[error("Could not find the .gitmodules file by id in the object database")] + FindExistingBlob(#[from] crate::object::find::existing::Error), + #[error("Did not find commit in current HEAD to access its tree")] + FindHeadCommit(#[from] crate::reference::head_commit::Error), + #[error(transparent)] + TreeFromCommit(#[from] crate::object::commit::Error), + } +} + +/// +pub mod is_active { + /// The error returned by [Submodule::is_active()](crate::Submodule::is_active()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + InitIsActivePlatform(#[from] gix_submodule::is_active_platform::Error), + #[error(transparent)] + QueryIsActive(#[from] gix_config::value::Error), + #[error(transparent)] + InitAttributes(#[from] crate::config::attribute_stack::Error), + #[error(transparent)] + InitPathspecDefaults(#[from] gix_pathspec::defaults::from_environment::Error), + #[error(transparent)] + ObtainIndex(#[from] crate::repository::index_or_load_from_head::Error), + } +} + +/// +pub mod fetch_recurse { + /// The error returned by [Submodule::fetch_recurse()](crate::Submodule::fetch_recurse()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + ModuleBoolean(#[from] gix_submodule::config::Error), + #[error(transparent)] + ConfigurationFallback(#[from] crate::config::key::GenericErrorWithValue), + } +} + +/// +pub mod open { + /// The error returned by [Submodule::open()](crate::Submodule::open()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + OpenRepository(#[from] crate::open::Error), + #[error(transparent)] + PathConfiguration(#[from] gix_submodule::config::path::Error), + } +} + +/// +pub mod index_id { + /// The error returned by [Submodule::index_id()](crate::Submodule::index_id()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + PathConfiguration(#[from] gix_submodule::config::path::Error), + #[error(transparent)] + Index(#[from] crate::repository::index_or_load_from_head::Error), + } +} + +/// +pub mod head_id { + /// The error returned by [Submodule::head_id()](crate::Submodule::head_id()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + HeadCommit(#[from] crate::reference::head_commit::Error), + #[error("Could not get tree of head commit")] + CommitTree(#[from] crate::object::commit::Error), + #[error("Could not peel tree to submodule path")] + PeelTree(#[from] crate::object::find::existing::Error), + #[error(transparent)] + PathConfiguration(#[from] gix_submodule::config::path::Error), + } +} diff --git a/vendor/gix/src/submodule/mod.rs b/vendor/gix/src/submodule/mod.rs new file mode 100644 index 000000000..52c5938fc --- /dev/null +++ b/vendor/gix/src/submodule/mod.rs @@ -0,0 +1,287 @@ +#![allow(clippy::result_large_err)] +//! Submodule plumbing and abstractions +//! +use std::{ + borrow::Cow, + cell::{Ref, RefCell, RefMut}, + path::PathBuf, +}; + +use gix_odb::FindExt; +pub use gix_submodule::*; + +use crate::{bstr::BStr, repository::IndexPersistedOrInMemory, Repository, Submodule}; + +pub(crate) type ModulesFileStorage = gix_features::threading::OwnShared<gix_fs::SharedFileSnapshotMut<File>>; +/// A lazily loaded and auto-updated worktree index. +pub type ModulesSnapshot = gix_fs::SharedFileSnapshot<File>; + +/// The name of the file containing (sub) module information. +pub(crate) const MODULES_FILE: &str = ".gitmodules"; + +mod errors; +pub use errors::*; + +/// A platform maintaining state needed to interact with submodules, created by [`Repository::submodules()]. +pub(crate) struct SharedState<'repo> { + pub(crate) repo: &'repo Repository, + pub(crate) modules: ModulesSnapshot, + is_active: RefCell<Option<IsActiveState>>, + index: RefCell<Option<IndexPersistedOrInMemory>>, +} + +impl<'repo> SharedState<'repo> { + pub(crate) fn new(repo: &'repo Repository, modules: ModulesSnapshot) -> Self { + SharedState { + repo, + modules, + is_active: RefCell::new(None), + index: RefCell::new(None), + } + } + + fn index(&self) -> Result<Ref<'_, IndexPersistedOrInMemory>, crate::repository::index_or_load_from_head::Error> { + { + let mut state = self.index.borrow_mut(); + if state.is_none() { + *state = self.repo.index_or_load_from_head()?.into(); + } + } + Ok(Ref::map(self.index.borrow(), |opt| { + opt.as_ref().expect("just initialized") + })) + } + + fn active_state_mut( + &self, + ) -> Result<(RefMut<'_, IsActivePlatform>, RefMut<'_, gix_worktree::Stack>), is_active::Error> { + let mut state = self.is_active.borrow_mut(); + if state.is_none() { + let platform = self + .modules + .is_active_platform(&self.repo.config.resolved, self.repo.config.pathspec_defaults()?)?; + let index = self.index()?; + let attributes = self + .repo + .attributes_only( + &index, + gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping + .adjust_for_bare(self.repo.is_bare()), + )? + .detach(); + *state = Some(IsActiveState { platform, attributes }); + } + Ok(RefMut::map_split(state, |opt| { + let state = opt.as_mut().expect("populated above"); + (&mut state.platform, &mut state.attributes) + })) + } +} + +struct IsActiveState { + platform: IsActivePlatform, + attributes: gix_worktree::Stack, +} + +///Access +impl<'repo> Submodule<'repo> { + /// Return the submodule's name. + pub fn name(&self) -> &BStr { + self.name.as_ref() + } + /// Return the path at which the submodule can be found, relative to the repository. + /// + /// For details, see [gix_submodule::File::path()]. + pub fn path(&self) -> Result<Cow<'_, BStr>, config::path::Error> { + self.state.modules.path(self.name()) + } + + /// Return the url from which to clone or update the submodule. + pub fn url(&self) -> Result<gix_url::Url, config::url::Error> { + self.state.modules.url(self.name()) + } + + /// Return the `update` field from this submodule's configuration, if present, or `None`. + pub fn update(&self) -> Result<Option<config::Update>, config::update::Error> { + self.state.modules.update(self.name()) + } + + /// Return the `branch` field from this submodule's configuration, if present, or `None`. + pub fn branch(&self) -> Result<Option<config::Branch>, config::branch::Error> { + self.state.modules.branch(self.name()) + } + + /// Return the `fetchRecurseSubmodules` field from this submodule's configuration, or retrieve the value from `fetch.recurseSubmodules` if unset. + pub fn fetch_recurse(&self) -> Result<Option<config::FetchRecurse>, fetch_recurse::Error> { + Ok(match self.state.modules.fetch_recurse(self.name())? { + Some(val) => Some(val), + None => self + .state + .repo + .config + .resolved + .boolean_by_key("fetch.recurseSubmodules") + .map(|res| crate::config::tree::Fetch::RECURSE_SUBMODULES.try_into_recurse_submodules(res)) + .transpose()?, + }) + } + + /// Return the `ignore` field from this submodule's configuration, if present, or `None`. + pub fn ignore(&self) -> Result<Option<config::Ignore>, config::Error> { + self.state.modules.ignore(self.name()) + } + + /// Return the `shallow` field from this submodule's configuration, if present, or `None`. + /// + /// If `true`, the submodule will be checked out with `depth = 1`. If unset, `false` is assumed. + pub fn shallow(&self) -> Result<Option<bool>, gix_config::value::Error> { + self.state.modules.shallow(self.name()) + } + + /// Returns true if this submodule is considered active and can thus participate in an operation. + /// + /// Please see the [plumbing crate documentation](gix_submodule::IsActivePlatform::is_active()) for details. + pub fn is_active(&self) -> Result<bool, is_active::Error> { + let (mut platform, mut attributes) = self.state.active_state_mut()?; + let is_active = platform.is_active(&self.state.repo.config.resolved, self.name.as_ref(), { + &mut |relative_path, case, is_dir, out| { + attributes + .set_case(case) + .at_entry(relative_path, Some(is_dir), |id, buf| { + self.state.repo.objects.find_blob(id, buf) + }) + .map_or(false, |platform| platform.matching_attributes(out)) + } + })?; + Ok(is_active) + } + + /// Return the object id of the submodule as stored in the index of the superproject, + /// or `None` if it was deleted from the index. + /// + /// If `None`, but `Some()` when calling [`Self::head_id()`], then the submodule was just deleted but the change + /// wasn't yet committed. Note that `None` is also returned if the entry at the submodule path isn't a submodule. + /// If `Some()`, but `None` when calling [`Self::head_id()`], then the submodule was just added without having committed the change. + pub fn index_id(&self) -> Result<Option<gix_hash::ObjectId>, index_id::Error> { + let path = self.path()?; + Ok(self + .state + .index()? + .entry_by_path(&path) + .and_then(|entry| (entry.mode == gix_index::entry::Mode::COMMIT).then_some(entry.id))) + } + + /// Return the object id of the submodule as stored in `HEAD^{tree}` of the superproject, or `None` if it wasn't yet committed. + /// + /// If `Some()`, but `None` when calling [`Self::index_id()`], then the submodule was just deleted but the change + /// wasn't yet committed. Note that `None` is also returned if the entry at the submodule path isn't a submodule. + /// If `None`, but `Some()` when calling [`Self::index_id()`], then the submodule was just added without having committed the change. + pub fn head_id(&self) -> Result<Option<gix_hash::ObjectId>, head_id::Error> { + let path = self.path()?; + Ok(self + .state + .repo + .head_commit()? + .tree()? + .peel_to_entry_by_path(gix_path::from_bstr(path.as_ref()))? + .and_then(|entry| (entry.mode() == gix_object::tree::EntryMode::Commit).then_some(entry.inner.oid))) + } + + /// Return the path at which the repository of the submodule should be located. + /// + /// The directory might not exist yet. + pub fn git_dir(&self) -> PathBuf { + self.state + .repo + .common_dir() + .join("modules") + .join(gix_path::from_bstr(self.name())) + } + + /// Return the path to the location at which the workdir would be checked out. + /// + /// Note that it may be a path relative to the repository if, for some reason, the parent directory + /// doesn't have a working dir set. + pub fn work_dir(&self) -> Result<PathBuf, config::path::Error> { + let worktree_git = gix_path::from_bstr(self.path()?); + Ok(match self.state.repo.work_dir() { + None => worktree_git.into_owned(), + Some(prefix) => prefix.join(worktree_git), + }) + } + + /// Return the path at which the repository of the submodule should be located, or the path inside of + /// the superproject's worktree where it actually *is* located if the submodule in the 'old-form', thus is a directory + /// inside of the superproject's work-tree. + /// + /// Note that 'old-form' paths returned aren't verified, i.e. the `.git` repository might be corrupt or otherwise + /// invalid - it's left to the caller to try to open it. + /// + /// Also note that the returned path may not actually exist. + pub fn git_dir_try_old_form(&self) -> Result<PathBuf, config::path::Error> { + let worktree_git = self.work_dir()?.join(gix_discover::DOT_GIT_DIR); + Ok(if worktree_git.is_dir() { + worktree_git + } else { + self.git_dir() + }) + } + + /// Query various parts of the submodule and assemble it into state information. + #[doc(alias = "status", alias = "git2")] + pub fn state(&self) -> Result<State, config::path::Error> { + let maybe_old_path = self.git_dir_try_old_form()?; + let git_dir = self.git_dir(); + let worktree_git = self.work_dir()?.join(gix_discover::DOT_GIT_DIR); + let superproject_configuration = self + .state + .repo + .config + .resolved + .sections_by_name("submodule") + .into_iter() + .flatten() + .any(|section| section.header().subsection_name() == Some(self.name.as_ref())); + Ok(State { + repository_exists: maybe_old_path.is_dir(), + is_old_form: maybe_old_path != git_dir, + worktree_checkout: worktree_git.exists(), + superproject_configuration, + }) + } + + /// Open the submodule as repository, or `None` if the submodule wasn't initialized yet. + /// + /// More states can be derived here: + /// + /// * *initialized* - a repository exists, i.e. `Some(repo)` and the working tree is present. + /// * *uninitialized* - a repository does not exist, i.e. `None` + /// * *deinitialized* - a repository does exist, i.e. `Some(repo)`, but its working tree is empty. + /// + /// Also see the [state()](Self::state()) method for learning about the submodule. + /// The repository can also be used to learn about the submodule `HEAD`, i.e. where its working tree is at, + /// which may differ compared to the superproject's index or `HEAD` commit. + pub fn open(&self) -> Result<Option<Repository>, open::Error> { + match crate::open_opts(self.git_dir_try_old_form()?, self.state.repo.options.clone()) { + Ok(repo) => Ok(Some(repo)), + Err(crate::open::Error::NotARepository { .. }) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +/// A summary of the state of all parts forming a submodule, which allows to answer various questions about it. +/// +/// Note that expensive questions about its presence in the `HEAD` or the `index` are left to the caller. +#[derive(Default, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct State { + /// if the submodule repository has been cloned. + pub repository_exists: bool, + /// if the submodule repository is located directly in the worktree of the superproject. + pub is_old_form: bool, + /// if the worktree is checked out. + pub worktree_checkout: bool, + /// If submodule configuration was found in the superproject's `.git/config` file. + /// Note that the presence of a single section is enough, independently of the actual values. + pub superproject_configuration: bool, +} |