Skip to content

Commit 54291fd

Browse files
committed
feat!: Provide a wrapper for gix_worktree::Stack for simpler attribute queries.
1 parent c79991c commit 54291fd

File tree

10 files changed

+136
-41
lines changed

10 files changed

+136
-41
lines changed

gix/src/attribute_stack.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use crate::bstr::BStr;
2+
use crate::types::AttributeStack;
3+
use crate::Repository;
4+
use gix_odb::FindExt;
5+
use std::ops::{Deref, DerefMut};
6+
7+
/// Lifecycle
8+
impl<'repo> AttributeStack<'repo> {
9+
/// Create a new instance from a `repo` and the underlying pre-configured `stack`.
10+
///
11+
/// Note that this type is typically created by [`Repository::attributes()`] or [`Repository::attributes_only()`].
12+
pub fn new(stack: gix_worktree::Stack, repo: &'repo Repository) -> Self {
13+
AttributeStack { repo, inner: stack }
14+
}
15+
16+
/// Detach the repository and return the underlying plumbing datatype.
17+
pub fn detach(self) -> gix_worktree::Stack {
18+
self.inner
19+
}
20+
}
21+
22+
impl Deref for AttributeStack<'_> {
23+
type Target = gix_worktree::Stack;
24+
25+
fn deref(&self) -> &Self::Target {
26+
&self.inner
27+
}
28+
}
29+
30+
impl DerefMut for AttributeStack<'_> {
31+
fn deref_mut(&mut self) -> &mut Self::Target {
32+
&mut self.inner
33+
}
34+
}
35+
36+
/// Platform retrieval
37+
impl<'repo> AttributeStack<'repo> {
38+
/// Append the `relative` path to the root directory of the cache and efficiently create leading directories, while assuring that no
39+
/// symlinks are in that path.
40+
/// Unless `is_dir` is known with `Some(…)`, then `relative` points to a directory itself in which case the entire resulting
41+
/// path is created as directory. If it's not known it is assumed to be a file.
42+
///
43+
/// Provide access to cached information for that `relative` path via the returned platform.
44+
pub fn at_path(
45+
&mut self,
46+
relative: impl AsRef<std::path::Path>,
47+
is_dir: Option<bool>,
48+
) -> std::io::Result<gix_worktree::stack::Platform<'_>> {
49+
self.inner
50+
.at_path(relative, is_dir, |id, buf| self.repo.objects.find_blob(id, buf))
51+
}
52+
53+
/// Obtain a platform for lookups from a repo-`relative` path, typically obtained from an index entry. `is_dir` should reflect
54+
/// whether it's a directory or not, or left at `None` if unknown.
55+
///
56+
/// If `relative` ends with `/` and `is_dir` is `None`, it is automatically assumed to be a directory.
57+
///
58+
/// ### Panics
59+
///
60+
/// - on illformed UTF8 in `relative`
61+
pub fn at_entry<'r>(
62+
&mut self,
63+
relative: impl Into<&'r BStr>,
64+
is_dir: Option<bool>,
65+
) -> std::io::Result<gix_worktree::stack::Platform<'_>> {
66+
self.inner
67+
.at_entry(relative, is_dir, |id, buf| self.repo.objects.find_blob(id, buf))
68+
}
69+
}

gix/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ mod ext;
117117
///
118118
pub mod prelude;
119119

120+
mod attribute_stack;
121+
120122
///
121123
pub mod path;
122124

@@ -129,7 +131,7 @@ pub(crate) type Config = OwnShared<gix_config::File<'static>>;
129131

130132
mod types;
131133
pub use types::{
132-
Commit, Head, Id, Object, ObjectDetached, Pathspec, Reference, Remote, Repository, Submodule, Tag,
134+
AttributeStack, Commit, Head, Id, Object, ObjectDetached, Pathspec, Reference, Remote, Repository, Submodule, Tag,
133135
ThreadSafeRepository, Tree, Worktree,
134136
};
135137

gix/src/pathspec.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use gix_odb::FindExt;
33
pub use gix_pathspec::*;
44

5-
use crate::{bstr::BStr, Pathspec, Repository};
5+
use crate::{bstr::BStr, AttributeStack, Pathspec, Repository};
66

77
///
88
pub mod init {
@@ -59,21 +59,28 @@ impl<'repo> Pathspec<'repo> {
5959
)?,
6060
)?;
6161
let cache = needs_cache.then(make_attributes).transpose()?;
62-
Ok(Self { repo, search, cache })
62+
Ok(Self {
63+
repo,
64+
search,
65+
stack: cache,
66+
})
6367
}
6468
/// Turn ourselves into the functional parts for direct usage.
65-
/// Note that the [`cache`](gix_worktree::Stack) is only set if one of the [`search` patterns](Search)
69+
/// Note that the [`cache`](AttributeStack) is only set if one of the [`search` patterns](Search)
6670
/// is specifying attributes to match for.
67-
pub fn into_parts(self) -> (Search, Option<gix_worktree::Stack>) {
68-
(self.search, self.cache)
71+
pub fn into_parts(self) -> (Search, Option<AttributeStack<'repo>>) {
72+
(
73+
self.search,
74+
self.stack.map(|stack| AttributeStack::new(stack, self.repo)),
75+
)
6976
}
7077
}
7178

7279
/// Access
7380
impl<'repo> Pathspec<'repo> {
7481
/// Return the attributes cache which is used when matching attributes in pathspecs, or `None` if none of the pathspecs require that.
7582
pub fn attributes(&self) -> Option<&gix_worktree::Stack> {
76-
self.cache.as_ref()
83+
self.stack.as_ref()
7784
}
7885

7986
/// Return the search itself which can be used for matching paths or accessing the actual patterns that will be used.
@@ -99,8 +106,8 @@ impl<'repo> Pathspec<'repo> {
99106
) -> Option<gix_pathspec::search::Match<'_>> {
100107
self.search
101108
.pattern_matching_relative_path(relative_path, is_dir, |relative_path, case, is_dir, out| {
102-
let cache = self.cache.as_mut().expect("initialized in advance");
103-
cache
109+
let stack = self.stack.as_mut().expect("initialized in advance");
110+
stack
104111
.set_case(case)
105112
.at_entry(relative_path, Some(is_dir), |id, buf| {
106113
self.repo.objects.find_blob(id, buf)

gix/src/repository/attributes.rs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! exclude information
2-
use crate::{config, Repository};
2+
use crate::{config, AttributeStack, Repository};
33

44
/// The error returned by [`Repository::attributes()`].
55
#[derive(Debug, thiserror::Error)]
@@ -24,15 +24,13 @@ impl Repository {
2424
///
2525
/// * `$XDG_CONFIG_HOME/…/ignore|attributes` if `core.excludesFile|attributesFile` is *not* set, otherwise use the configured file.
2626
/// * `$GIT_DIR/info/exclude|attributes` if present.
27-
// TODO: test, provide higher-level custom Cache wrapper that is much easier to use and doesn't panic when accessing entries
28-
// by non-relative path.
2927
pub fn attributes(
3028
&self,
3129
index: &gix_index::State,
3230
attributes_source: gix_worktree::stack::state::attributes::Source,
3331
ignore_source: gix_worktree::stack::state::ignore::Source,
3432
exclude_overrides: Option<gix_ignore::Search>,
35-
) -> Result<gix_worktree::Stack, Error> {
33+
) -> Result<AttributeStack<'_>, Error> {
3634
let case = if self.config.ignore_case {
3735
gix_glob::pattern::Case::Fold
3836
} else {
@@ -48,13 +46,16 @@ impl Repository {
4846
.assemble_exclude_globals(self.git_dir(), exclude_overrides, ignore_source, &mut buf)?;
4947
let state = gix_worktree::stack::State::AttributesAndIgnoreStack { attributes, ignore };
5048
let attribute_list = state.id_mappings_from_index(index, index.path_backing(), case);
51-
Ok(gix_worktree::Stack::new(
52-
// this is alright as we don't cause mutation of that directory, it's virtual.
53-
self.work_dir().unwrap_or(self.git_dir()),
54-
state,
55-
case,
56-
buf,
57-
attribute_list,
49+
Ok(AttributeStack::new(
50+
gix_worktree::Stack::new(
51+
// this is alright as we don't cause mutation of that directory, it's virtual.
52+
self.work_dir().unwrap_or(self.git_dir()),
53+
state,
54+
case,
55+
buf,
56+
attribute_list,
57+
),
58+
self,
5859
))
5960
}
6061

@@ -63,7 +64,7 @@ impl Repository {
6364
&self,
6465
index: &gix_index::State,
6566
attributes_source: gix_worktree::stack::state::attributes::Source,
66-
) -> Result<gix_worktree::Stack, config::attribute_stack::Error> {
67+
) -> Result<AttributeStack<'_>, config::attribute_stack::Error> {
6768
let case = if self.config.ignore_case {
6869
gix_glob::pattern::Case::Fold
6970
} else {
@@ -76,13 +77,16 @@ impl Repository {
7677
)?;
7778
let state = gix_worktree::stack::State::AttributesStack(attributes);
7879
let attribute_list = state.id_mappings_from_index(index, index.path_backing(), case);
79-
Ok(gix_worktree::Stack::new(
80-
// this is alright as we don't cause mutation of that directory, it's virtual.
81-
self.work_dir().unwrap_or(self.git_dir()),
82-
state,
83-
case,
84-
buf,
85-
attribute_list,
80+
Ok(AttributeStack::new(
81+
gix_worktree::Stack::new(
82+
// this is alright as we don't cause mutation of that directory, it's virtual.
83+
self.work_dir().unwrap_or(self.git_dir()),
84+
state,
85+
case,
86+
buf,
87+
attribute_list,
88+
),
89+
self,
8690
))
8791
}
8892

gix/src/repository/filter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,6 @@ impl Repository {
5959
)?;
6060
(cache, IndexPersistedOrInMemory::Persisted(index))
6161
};
62-
Ok((filter::Pipeline::new(self, cache)?, index))
62+
Ok((filter::Pipeline::new(self, cache.detach())?, index))
6363
}
6464
}

gix/src/repository/pathspec.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use gix_pathspec::MagicSignature;
22

3-
use crate::{bstr::BStr, config::cache::util::ApplyLeniencyDefault, Pathspec, Repository};
3+
use crate::{bstr::BStr, config::cache::util::ApplyLeniencyDefault, AttributeStack, Pathspec, Repository};
44

55
impl Repository {
66
/// Create a new pathspec abstraction that allows to conduct searches using `patterns`.
@@ -20,7 +20,9 @@ impl Repository {
2020
attributes_source: gix_worktree::stack::state::attributes::Source,
2121
) -> Result<Pathspec<'_>, crate::pathspec::init::Error> {
2222
Pathspec::new(self, patterns, inherit_ignore_case, || {
23-
self.attributes_only(index, attributes_source).map_err(Into::into)
23+
self.attributes_only(index, attributes_source)
24+
.map(AttributeStack::detach)
25+
.map_err(Into::into)
2426
})
2527
}
2628

gix/src/repository/worktree.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ impl crate::Repository {
7575
// TODO(perf): when loading a non-HEAD tree, we effectively traverse the tree twice. This is usually fast though, and sharing
7676
// an object cache between the copies of the ODB handles isn't trivial and needs a lock.
7777
let index = self.index_from_tree(&id)?;
78-
let mut cache = self.attributes_only(&index, gix_worktree::stack::state::attributes::Source::IdMapping)?;
78+
let mut cache = self
79+
.attributes_only(&index, gix_worktree::stack::state::attributes::Source::IdMapping)?
80+
.detach();
7981
let pipeline =
8082
gix_filter::Pipeline::new(cache.attributes_collection(), crate::filter::Pipeline::options(self)?);
8183
let objects = self.objects.clone().into_arc().expect("TBD error handling");

gix/src/submodule/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,14 @@ impl<'repo> SharedState<'repo> {
6161
.modules
6262
.is_active_platform(&self.repo.config.resolved, self.repo.config.pathspec_defaults()?)?;
6363
let index = self.index()?;
64-
let attributes = self.repo.attributes_only(
65-
&index,
66-
gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping
67-
.adjust_for_bare(self.repo.is_bare()),
68-
)?;
64+
let attributes = self
65+
.repo
66+
.attributes_only(
67+
&index,
68+
gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping
69+
.adjust_for_bare(self.repo.is_bare()),
70+
)?
71+
.detach();
6972
*state = Some(IsActiveState { platform, attributes });
7073
}
7174
Ok(RefMut::map_split(state, |opt| {

gix/src/types.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ pub struct Remote<'repo> {
208208
pub struct Pathspec<'repo> {
209209
pub(crate) repo: &'repo Repository,
210210
/// The cache to power attribute access. It's only initialized if we have a pattern with attributes.
211-
pub(crate) cache: Option<gix_worktree::Stack>,
211+
pub(crate) stack: Option<gix_worktree::Stack>,
212212
/// The prepared search to use for checking matches.
213213
pub(crate) search: gix_pathspec::Search,
214214
}
@@ -219,3 +219,9 @@ pub struct Submodule<'repo> {
219219
pub(crate) state: Rc<crate::submodule::SharedState<'repo>>,
220220
pub(crate) name: BString,
221221
}
222+
223+
/// A utility to access `.gitattributes` and `.gitignore` information efficiently.
224+
pub struct AttributeStack<'repo> {
225+
pub(crate) repo: &'repo Repository,
226+
pub(crate) inner: gix_worktree::Stack,
227+
}

gix/src/worktree/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pub mod excludes {
145145

146146
///
147147
pub mod attributes {
148-
use crate::Worktree;
148+
use crate::{AttributeStack, Worktree};
149149

150150
/// The error returned by [`Worktree::attributes()`].
151151
#[derive(Debug, thiserror::Error)]
@@ -164,7 +164,7 @@ pub mod attributes {
164164
///
165165
/// * `$XDG_CONFIG_HOME/…/ignore|attributes` if `core.excludesFile|attributesFile` is *not* set, otherwise use the configured file.
166166
/// * `$GIT_DIR/info/exclude|attributes` if present.
167-
pub fn attributes(&self, overrides: Option<gix_ignore::Search>) -> Result<gix_worktree::Stack, Error> {
167+
pub fn attributes(&self, overrides: Option<gix_ignore::Search>) -> Result<AttributeStack<'repo>, Error> {
168168
let index = self.index()?;
169169
Ok(self.parent.attributes(
170170
&index,
@@ -175,7 +175,7 @@ pub mod attributes {
175175
}
176176

177177
/// Like [attributes()][Self::attributes()], but without access to exclude/ignore information.
178-
pub fn attributes_only(&self) -> Result<gix_worktree::Stack, Error> {
178+
pub fn attributes_only(&self) -> Result<AttributeStack<'repo>, Error> {
179179
let index = self.index()?;
180180
self.parent
181181
.attributes_only(

0 commit comments

Comments
 (0)