Skip to content

Commit 9c06d99

Browse files
committed
feat: add Repository::attributes() and Worktree::attributes().
1 parent 26e6a66 commit 9c06d99

File tree

9 files changed

+154
-38
lines changed

9 files changed

+154
-38
lines changed

crate-status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/gix-lock/README.
690690
* [x] proper handling of worktree related refs
691691
* [ ] create, move, remove, and repair
692692
* [x] access exclude information
693-
* [ ] access attribute information
693+
* [x] access attribute information
694694
* [x] respect `core.worktree` configuration
695695
- **deviation**
696696
* The delicate interplay between `GIT_COMMON_DIR` and `GIT_WORK_TREE` isn't implemented.

gix/src/attributes.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// The error returned by [`Repository::attributes()`].
2+
#[derive(Debug, thiserror::Error)]
3+
#[allow(missing_docs)]
4+
pub enum Error {
5+
#[error(transparent)]
6+
ConfigureAttributes(#[from] crate::config::attribute_stack::Error),
7+
#[error(transparent)]
8+
ConfigureExcludes(#[from] crate::config::exclude_stack::Error),
9+
}

gix/src/config/cache/access.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,13 @@ impl Cache {
167167
symlink: boolean(self, "core.symlinks", &Core::SYMLINKS, true)?,
168168
};
169169
Ok(gix_worktree::checkout::Options {
170-
attributes: self.assemble_attribute_globals(
171-
git_dir,
172-
gix_worktree::cache::state::attributes::Source::IdMappingThenWorktree,
173-
self.attributes,
174-
)?,
170+
attributes: self
171+
.assemble_attribute_globals(
172+
git_dir,
173+
gix_worktree::cache::state::attributes::Source::IdMappingThenWorktree,
174+
self.attributes,
175+
)?
176+
.0,
175177
fs: capabilities,
176178
thread_limit,
177179
destination_is_initially_empty: false,
@@ -192,13 +194,29 @@ impl Cache {
192194
})
193195
}
194196

197+
pub(crate) fn assemble_exclude_globals(
198+
&self,
199+
git_dir: &std::path::Path,
200+
overrides: Option<gix_ignore::Search>,
201+
buf: &mut Vec<u8>,
202+
) -> Result<gix_worktree::cache::state::Ignore, config::exclude_stack::Error> {
203+
let excludes_file = match self.excludes_file().transpose()? {
204+
Some(user_path) => Some(user_path),
205+
None => self.xdg_config_path("ignore")?,
206+
};
207+
Ok(gix_worktree::cache::state::Ignore::new(
208+
overrides.unwrap_or_default(),
209+
gix_ignore::Search::from_git_dir(git_dir, excludes_file, buf)?,
210+
None,
211+
))
212+
}
195213
// TODO: at least one test, maybe related to core.attributesFile configuration.
196-
fn assemble_attribute_globals(
214+
pub(crate) fn assemble_attribute_globals(
197215
&self,
198216
git_dir: &std::path::Path,
199217
source: gix_worktree::cache::state::attributes::Source,
200218
attributes: crate::open::permissions::Attributes,
201-
) -> Result<gix_worktree::cache::state::Attributes, config::attribute_stack::Error> {
219+
) -> Result<(gix_worktree::cache::state::Attributes, Vec<u8>), config::attribute_stack::Error> {
202220
let configured_or_user_attributes = match self
203221
.trusted_file_path("core", None, Core::ATTRIBUTES_FILE.name)
204222
.transpose()?
@@ -224,12 +242,13 @@ impl Cache {
224242
let info_attributes_path = git_dir.join("info").join("attributes");
225243
let mut buf = Vec::new();
226244
let mut collection = gix_attributes::search::MetadataCollection::default();
227-
Ok(gix_worktree::cache::state::Attributes::new(
245+
let res = gix_worktree::cache::state::Attributes::new(
228246
gix_attributes::Search::new_globals(attribute_files, &mut buf, &mut collection)?,
229247
Some(info_attributes_path),
230248
source,
231249
collection,
232-
))
250+
);
251+
Ok((res, buf))
233252
}
234253

235254
pub(crate) fn xdg_config_path(

gix/src/config/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ pub mod checkout_options {
118118
}
119119
}
120120

121+
///
122+
pub mod exclude_stack {
123+
use std::path::PathBuf;
124+
125+
/// The error produced when setting up a stack to query `gitignore` information.
126+
#[derive(Debug, thiserror::Error)]
127+
#[allow(missing_docs)]
128+
pub enum Error {
129+
#[error("Could not read repository exclude")]
130+
Io(#[from] std::io::Error),
131+
#[error(transparent)]
132+
EnvironmentPermission(#[from] gix_sec::permission::Error<PathBuf>),
133+
#[error("The value for `core.excludesFile` could not be read from configuration")]
134+
ExcludesFilePathInterpolation(#[from] gix_config::path::interpolate::Error),
135+
}
136+
}
137+
121138
///
122139
pub mod attribute_stack {
123140
/// The error produced when setting up the attribute stack to query `gitattributes`.

gix/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ pub use hash::{oid, ObjectId};
9999

100100
pub mod interrupt;
101101

102+
///
103+
pub mod attributes;
104+
102105
mod ext;
103106
///
104107
pub mod prelude;
@@ -127,7 +130,7 @@ pub mod head;
127130
pub mod id;
128131
pub mod object;
129132
pub mod reference;
130-
pub mod repository;
133+
mod repository;
131134
pub mod tag;
132135

133136
///

gix/src/repository/attributes.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//! exclude information
2+
use crate::Repository;
3+
4+
impl Repository {
5+
/// Configure a file-system cache for accessing git attributes *and* excludes on a per-path basis.
6+
///
7+
/// Use `attribute_source` to specify where to read attributes from. Also note that exclude information will
8+
/// always try to read `.gitignore` files from disk before trying to read it from the `index`.
9+
///
10+
/// Note that no worktree is required for this to work, even though access to in-tree `.gitattributes` and `.gitignore` files
11+
/// would require a non-empty `index` that represents a git tree.
12+
///
13+
/// This takes into consideration all the usual repository configuration, namely:
14+
///
15+
/// * `$XDG_CONFIG_HOME/…/ignore|attributes` if `core.excludesFile|attributesFile` is *not* set, otherwise use the configured file.
16+
/// * `$GIT_DIR/info/exclude|attributes` if present.
17+
// TODO: test, provide higher-level custom Cache wrapper that is much easier to use and doesn't panic when accessing entries
18+
// by non-relative path.
19+
pub fn attributes(
20+
&self,
21+
index: &gix_index::State,
22+
source: gix_worktree::cache::state::attributes::Source,
23+
exclude_overrides: Option<gix_ignore::Search>,
24+
) -> Result<gix_worktree::Cache, crate::attributes::Error> {
25+
let case = if self.config.ignore_case {
26+
gix_glob::pattern::Case::Fold
27+
} else {
28+
gix_glob::pattern::Case::Sensitive
29+
};
30+
let (attributes, mut buf) =
31+
self.config
32+
.assemble_attribute_globals(self.git_dir(), source, self.options.permissions.attributes)?;
33+
let ignore = self
34+
.config
35+
.assemble_exclude_globals(self.git_dir(), exclude_overrides, &mut buf)?;
36+
let state = gix_worktree::cache::State::AttributesAndIgnoreStack { attributes, ignore };
37+
let attribute_list = state.id_mappings_from_index(index, index.path_backing(), case);
38+
Ok(gix_worktree::Cache::new(
39+
// this is alright as we don't cause mutation of that directory, it's virtual.
40+
self.work_dir().unwrap_or(self.git_dir()),
41+
state,
42+
case,
43+
buf,
44+
attribute_list,
45+
))
46+
}
47+
}

gix/src/repository/excludes.rs

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
11
//! exclude information
2-
use crate::Repository;
3-
use std::path::PathBuf;
4-
5-
/// The error returned by [`Repository::excludes()`].
6-
#[derive(Debug, thiserror::Error)]
7-
#[allow(missing_docs)]
8-
pub enum Error {
9-
#[error("Could not read repository exclude")]
10-
Io(#[from] std::io::Error),
11-
#[error(transparent)]
12-
EnvironmentPermission(#[from] gix_sec::permission::Error<PathBuf>),
13-
#[error("The value for `core.excludesFile` could not be read from configuration")]
14-
ExcludesFilePathInterpolation(#[from] gix_config::path::interpolate::Error),
15-
}
16-
2+
use crate::{config, Repository};
173
impl Repository {
184
/// Configure a file-system cache checking if files below the repository are excluded.
195
///
@@ -24,28 +10,26 @@ impl Repository {
2410
///
2511
/// * `$XDG_CONFIG_HOME/…/ignore` if `core.excludesFile` is *not* set, otherwise use the configured file.
2612
/// * `$GIT_DIR/info/exclude` if present.
13+
///
14+
/// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use
15+
/// [`Repository::attributes()`] for accessing both attributes and excludes.
2716
// TODO: test, provide higher-level custom Cache wrapper that is much easier to use and doesn't panic when accessing entries
2817
// by non-relative path.
2918
pub fn excludes(
3019
&self,
3120
index: &gix_index::State,
3221
overrides: Option<gix_ignore::Search>,
33-
) -> Result<gix_worktree::Cache, Error> {
22+
) -> Result<gix_worktree::Cache, config::exclude_stack::Error> {
3423
let case = if self.config.ignore_case {
3524
gix_glob::pattern::Case::Fold
3625
} else {
3726
gix_glob::pattern::Case::Sensitive
3827
};
3928
let mut buf = Vec::with_capacity(512);
40-
let excludes_file = match self.config.excludes_file().transpose()? {
41-
Some(user_path) => Some(user_path),
42-
None => self.config.xdg_config_path("ignore")?,
43-
};
44-
let state = gix_worktree::cache::State::IgnoreStack(gix_worktree::cache::state::Ignore::new(
45-
overrides.unwrap_or_default(),
46-
gix_ignore::Search::from_git_dir(self.git_dir(), excludes_file, &mut buf)?,
47-
None,
48-
));
29+
let ignore = self
30+
.config
31+
.assemble_exclude_globals(self.git_dir(), overrides, &mut buf)?;
32+
let state = gix_worktree::cache::State::IgnoreStack(ignore);
4933
let attribute_list = state.id_mappings_from_index(index, index.path_backing(), case);
5034
Ok(gix_worktree::Cache::new(
5135
// this is alright as we don't cause mutation of that directory, it's virtual.

gix/src/repository/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ impl crate::Repository {
1919
}
2020
}
2121

22+
mod attributes;
2223
mod cache;
2324
mod config;
24-
pub mod excludes;
25+
mod excludes;
2526
pub(crate) mod identity;
2627
mod impls;
2728
mod init;

gix/src/worktree/mod.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub mod excludes {
103103
#[error(transparent)]
104104
OpenIndex(#[from] crate::worktree::open_index::Error),
105105
#[error(transparent)]
106-
CreateCache(#[from] crate::repository::excludes::Error),
106+
CreateCache(#[from] crate::config::exclude_stack::Error),
107107
}
108108

109109
impl<'repo> crate::Worktree<'repo> {
@@ -113,9 +113,45 @@ pub mod excludes {
113113
///
114114
/// * `$XDG_CONFIG_HOME/…/ignore` if `core.excludesFile` is *not* set, otherwise use the configured file.
115115
/// * `$GIT_DIR/info/exclude` if present.
116+
///
117+
/// When only excludes are desired, this is the most efficient way to obtain them. Otherwise use
118+
/// [`Worktree::attributes()`][crate::Worktree::attributes()] for accessing both attributes and excludes.
116119
pub fn excludes(&self, overrides: Option<gix_ignore::Search>) -> Result<gix_worktree::Cache, Error> {
117120
let index = self.index()?;
118121
Ok(self.parent.excludes(&index, overrides)?)
119122
}
120123
}
121124
}
125+
126+
///
127+
pub mod attributes {
128+
/// The error returned by [`Worktree::attributes()`][crate::Worktree::attributes()].
129+
#[derive(Debug, thiserror::Error)]
130+
#[allow(missing_docs)]
131+
pub enum Error {
132+
#[error(transparent)]
133+
OpenIndex(#[from] crate::worktree::open_index::Error),
134+
#[error(transparent)]
135+
CreateCache(#[from] crate::attributes::Error),
136+
}
137+
138+
impl<'repo> crate::Worktree<'repo> {
139+
/// Configure a file-system cache checking if files below the repository are excluded or for querying their attributes.
140+
///
141+
/// Use `attribute_source` to specify where to read attributes from. Also note that exclude information will
142+
/// always try to read `.gitignore` files from disk before trying to read it from the `index`.
143+
///
144+
/// This takes into consideration all the usual repository configuration, namely:
145+
///
146+
/// * `$XDG_CONFIG_HOME/…/ignore|attributes` if `core.excludesFile|attributesFile` is *not* set, otherwise use the configured file.
147+
/// * `$GIT_DIR/info/exclude|attributes` if present.
148+
pub fn attributes(
149+
&self,
150+
source: gix_worktree::cache::state::attributes::Source,
151+
overrides: Option<gix_ignore::Search>,
152+
) -> Result<gix_worktree::Cache, Error> {
153+
let index = self.index()?;
154+
Ok(self.parent.attributes(&index, source, overrides)?)
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)