Skip to content

Commit

Permalink
feat: add Submodule type to represent a declared submodule.
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Aug 21, 2023
1 parent a7c0880 commit 59bb3c4
Show file tree
Hide file tree
Showing 29 changed files with 934 additions and 61 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -726,16 +726,16 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/gix-lock/README.
* [ ] a way to make changes to individual configuration files
* [x] mailmap
* [x] object replacements (`git replace`)
* [ ] configuration
* [x] read git configuration
* [ ] merging
* [ ] stashing
* [ ] Use _Commit Graph_ to speed up certain queries
* [ ] subtree
* [ ] interactive rebase status/manipulation
* **submodules**
* [ ] handle 'old' form for reading
* [ ] list
* [ ] traverse recursively
* [x] handle 'old' form for reading and detect old form
* [x] list
* [ ] edit
* [ ] API documentation
* [ ] Some examples

Expand Down
1 change: 1 addition & 0 deletions gix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ gix-worktree-state = { version = "^0.1.0", path = "../gix-worktree-state" }
gix-hashtable = { version = "^0.2.4", path = "../gix-hashtable" }
gix-commitgraph = { version = "^0.18.2", path = "../gix-commitgraph" }
gix-pathspec = { version = "^0.1.0", path = "../gix-pathspec" }
gix-submodule = { version = "^0.1.0", path = "../gix-submodule" }

gix-worktree-stream = { version = "^0.3.0", path = "../gix-worktree-stream", optional = true }
gix-archive = { version = "^0.3.0", path = "../gix-archive", default-features = false, optional = true }
Expand Down
1 change: 0 additions & 1 deletion gix/src/config/cache/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ impl Cache {
Ok((state, buf))
}

/// // TODO: let caller control if base-case-sensitivity should be inherited from the file-system if it isn't overridden in globals.
pub(crate) fn pathspec_defaults(
&self,
) -> Result<gix_pathspec::Defaults, gix_pathspec::defaults::from_environment::Error> {
Expand Down
32 changes: 30 additions & 2 deletions gix/src/config/tree/sections/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ impl Fetch {
&config::Tree::FETCH,
validate::NegotiationAlgorithm,
);
/// The `fetch.recurseSubmodules` key.
pub const RECURSE_SUBMODULES: RecurseSubmodules =
RecurseSubmodules::new_with_validate("recurseSubmodules", &config::Tree::FETCH, validate::RecurseSubmodules);
}

impl Section for Fetch {
Expand All @@ -18,21 +21,27 @@ impl Section for Fetch {
}

fn keys(&self) -> &[&dyn Key] {
&[&Self::NEGOTIATION_ALGORITHM]
&[&Self::NEGOTIATION_ALGORITHM, &Self::RECURSE_SUBMODULES]
}
}

/// The `fetch.negotiationAlgorithm` key.
pub type NegotiationAlgorithm = keys::Any<validate::NegotiationAlgorithm>;

/// The `fetch.recurseSubmodules` key.
pub type RecurseSubmodules = keys::Any<validate::RecurseSubmodules>;

mod algorithm {
use std::borrow::Cow;

use gix_object::bstr::ByteSlice;

use crate::{
bstr::BStr,
config::{key::GenericErrorWithValue, tree::sections::fetch::NegotiationAlgorithm},
config::{
key::GenericErrorWithValue,
tree::sections::fetch::{NegotiationAlgorithm, RecurseSubmodules},
},
remote::fetch::negotiate,
};

Expand All @@ -50,6 +59,16 @@ mod algorithm {
})
}
}

impl RecurseSubmodules {
/// Obtain the way submodules should be updated.
pub fn try_into_recurse_submodules(
&'static self,
value: Result<bool, gix_config::value::Error>,
) -> Result<gix_submodule::config::FetchRecurse, GenericErrorWithValue> {
gix_submodule::config::FetchRecurse::new(value).map_err(|err| GenericErrorWithValue::from_value(self, err))
}
}
}

mod validate {
Expand All @@ -65,4 +84,13 @@ mod validate {
Ok(())
}
}

pub struct RecurseSubmodules;
impl keys::Validate for RecurseSubmodules {
fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let boolean = gix_config::Boolean::try_from(value).map(|b| b.0);
Fetch::RECURSE_SUBMODULES.try_into_recurse_submodules(boolean)?;
Ok(())
}
}
}
24 changes: 14 additions & 10 deletions gix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,18 @@
//!
//! This section is a 'striving to be complete' mapping from `libgit2` APIs to the respective methods in `gix`.
//!
//! * [`git2::Repository::open()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open) ➡ [`gix::open()`](https://docs.rs/gix/*/gix/fn.open.html)
//! * [`git2::Repository::open()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open) ➡ [`open()`]
//! * [`git2::Repository::open_bare()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open_bare) ➡ ❌
//! * [`git2::Repository::open_from_env()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open_from_env) ➡ [`gix::ThreadSafeRepository::open_with_environment_overrides()`](https://docs.rs/gix/*/gix/struct.ThreadSafeRepository.html#method.open_with_environment_overrides)
//! * [`git2::Repository::open_ext()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open_ext) ➡ [`gix::open_opts()`](https://docs.rs/gix/*/gix/fn.open_opts.html)
//! * [`git2::Repository::revparse()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.revparse) ➡ [`gix::Repository::rev_parse()`](https://docs.rs/gix/*/gix/struct.Repository.html#method.rev_parse)
//! * [`git2::Repository::revparse_single()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.revparse_single) ➡ [`gix::Repository::rev_parse_single()`](https://docs.rs/gix/*/gix/struct.Repository.html#method.rev_parse_single)
//! * [`git2::Repository::revwalk()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.revwalk) ➡ [`gix::Repository::rev_walk()`](https://docs.rs/gix/*/gix/struct.Repository.html#method.rev_walk)
//! * [`git2::build::CheckoutBuilder::disable_filters()](https://docs.rs/git2/0.17.2/git2/build/struct.CheckoutBuilder.html#method.disable_filters) ➡ ❌ *(filters are always applied during checkouts)*
//! * [`git2::Odb::read_header()`](https://docs.rs/git2/0.17.2/git2/struct.Odb.html#method.read_header) ➡ [`gix::Repository::find_header()`](https://docs.rs/gix/*/gix/struct.Repository.html#method.find_header)
//! * [`git2::Repository::open_from_env()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open_from_env) ➡ [`ThreadSafeRepository::open_with_environment_overrides()`]
//! * [`git2::Repository::open_ext()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open_ext) ➡ [`open_opts()`]
//! * [`git2::Repository::revparse()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.revparse) ➡ [`Repository::rev_parse()`]
//! * [`git2::Repository::revparse_single()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.revparse_single) ➡ [`Repository::rev_parse_single()`]
//! * [`git2::Repository::revwalk()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.revwalk) ➡ [`Repository::rev_walk()`]
//! * [`git2::build::CheckoutBuilder::disable_filters()](https://docs.rs/git2/*/git2/build/struct.CheckoutBuilder.html#method.disable_filters) ➡ ❌ *(filters are always applied during checkouts)*
//! * [`git2::Odb::read_header()`](https://docs.rs/git2/*/git2/struct.Odb.html#method.read_header) ➡ [`Repository::find_header()`]
//! * [`git2::Repository::submodules()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.submodules) ➡ [`Repository::submodules()`]
//! * [`git2::Repository::submodules()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.submodules) ➡ [`Repository::submodules()`]
//! * [`git2::Repository::submodule_status()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.submodule_status) ➡ [`Submodule::state()`] - status provides more information and conveniences though, and an actual worktree status isn't performed.
//!
//! ## Feature Flags
#![cfg_attr(
Expand Down Expand Up @@ -132,8 +135,8 @@ pub(crate) type Config = OwnShared<gix_config::File<'static>>;

mod types;
pub use types::{
Commit, Head, Id, Object, ObjectDetached, Pathspec, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree,
Worktree,
Commit, Head, Id, Object, ObjectDetached, Pathspec, Reference, Remote, Repository, Submodule, Tag,
ThreadSafeRepository, Tree, Worktree,
};

///
Expand All @@ -145,6 +148,7 @@ pub mod object;
pub mod pathspec;
pub mod reference;
pub mod repository;
pub mod submodule;
pub mod tag;

///
Expand Down
1 change: 1 addition & 0 deletions gix/src/open/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ impl ThreadSafeRepository {
linked_worktree_options: options,
index: gix_fs::SharedFileSnapshotMut::new().into(),
shallow_commits: gix_fs::SharedFileSnapshotMut::new().into(),
modules: gix_fs::SharedFileSnapshotMut::new().into(),
})
}
}
Expand Down
9 changes: 2 additions & 7 deletions gix/src/pathspec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ pub mod init {
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Filesystem configuration could not be obtained")]
FilesystemConfig(#[from] crate::config::boolean::Error),
#[error(transparent)]
MakeAttributes(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error(transparent)]
Defaults(#[from] gix_pathspec::defaults::from_environment::Error),
Defaults(#[from] crate::repository::pathspec_defaults_ignore_case::Error),
#[error(transparent)]
ParseSpec(#[from] gix_pathspec::parse::Error),
#[error(
Expand Down Expand Up @@ -45,10 +43,7 @@ impl<'repo> Pathspec<'repo> {
inherit_ignore_case: bool,
make_attributes: impl FnOnce() -> Result<gix_worktree::Stack, Box<dyn std::error::Error + Send + Sync + 'static>>,
) -> Result<Self, init::Error> {
let mut defaults = repo.pathspec_defaults()?;
if inherit_ignore_case && repo.config.fs_capabilities()?.ignore_case {
defaults.signature |= MagicSignature::ICASE;
}
let defaults = repo.pathspec_defaults_inherit_ignore_case(inherit_ignore_case)?;
let patterns = patterns
.into_iter()
.map(move |p| parse(p.as_ref(), defaults))
Expand Down
4 changes: 4 additions & 0 deletions gix/src/repository/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ impl Clone for crate::Repository {
self.options.clone(),
self.index.clone(),
self.shallow_commits.clone(),
self.modules.clone(),
)
}
}
Expand Down Expand Up @@ -42,6 +43,7 @@ impl From<&crate::ThreadSafeRepository> for crate::Repository {
repo.linked_worktree_options.clone(),
repo.index.clone(),
repo.shallow_commits.clone(),
repo.modules.clone(),
)
}
}
Expand All @@ -57,6 +59,7 @@ impl From<crate::ThreadSafeRepository> for crate::Repository {
repo.linked_worktree_options,
repo.index,
repo.shallow_commits,
repo.modules.clone(),
)
}
}
Expand All @@ -71,6 +74,7 @@ impl From<crate::Repository> for crate::ThreadSafeRepository {
config: r.config,
linked_worktree_options: r.options,
index: r.index,
modules: r.modules,
shallow_commits: r.shallow_commits,
}
}
Expand Down
57 changes: 31 additions & 26 deletions gix/src/repository/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,35 @@ impl crate::Repository {
///
/// 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()),
)),
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.
Expand All @@ -69,13 +75,12 @@ impl crate::Repository {
pub fn index_or_load_from_head(
&self,
) -> Result<IndexPersistedOrInMemory, crate::repository::index_or_load_from_head::Error> {
Ok(match self.index() {
Ok(index) => IndexPersistedOrInMemory::Persisted(index),
Err(worktree::open_index::Error::IndexFile(_)) => {
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)?)
}
Err(err) => return Err(err.into()),
})
}

Expand Down
2 changes: 2 additions & 0 deletions gix/src/repository/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ impl crate::Repository {
linked_worktree_options: crate::open::Options,
index: crate::worktree::IndexStorage,
shallow_commits: crate::shallow::CommitsStorage,
modules: crate::submodule::ModulesFileStorage,
) -> Self {
setup_objects(&mut objects, &config);
crate::Repository {
Expand All @@ -23,6 +24,7 @@ impl crate::Repository {
options: linked_worktree_options,
index,
shallow_commits,
modules,
}
}

Expand Down
5 changes: 5 additions & 0 deletions gix/src/repository/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ impl crate::Repository {
self.git_dir().join("index")
}

/// The path to the `.gitmodules` file in the worktree, if a worktree is available.
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()
Expand Down
16 changes: 16 additions & 0 deletions gix/src/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -53,6 +55,7 @@ mod remote;
mod revision;
mod shallow;
mod state;
mod submodule;
mod thread_safe;
mod worktree;

Expand All @@ -66,6 +69,19 @@ pub enum IndexPersistedOrInMemory {
InMemory(gix_index::File),
}

///
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),
}
}

///
pub mod index_or_load_from_head {
/// The error returned by [`Repository::index_or_load_from_head()`][crate::Repository::index_or_load_from_head()].
Expand Down
Loading

0 comments on commit 59bb3c4

Please sign in to comment.