Skip to content

Commit 64d9a18

Browse files
committed
feat: open::Options now allow controlling where gitattributes files are loaded from.
That way it's possible to, for example, isolate all operations that rely on the `gitattribute` system, like checkouts or additions to the index.
1 parent ef1a499 commit 64d9a18

File tree

9 files changed

+225
-80
lines changed

9 files changed

+225
-80
lines changed

gix/src/config/cache/access.rs

+66-25
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#![allow(clippy::result_large_err)]
2+
use gix_attributes::Source;
3+
use gix_glob::pattern::Case;
24
use std::{borrow::Cow, path::PathBuf, time::Duration};
35

46
use gix_lock::acquire::Fail;
@@ -154,33 +156,30 @@ impl Cache {
154156
.unwrap_or(default))
155157
}
156158

157-
fn assemble_attribute_globals(
158-
me: &Cache,
159-
_git_dir: &std::path::Path,
160-
) -> Result<gix_attributes::Search, checkout_options::Error> {
161-
let _attributes_file = match me
162-
.trusted_file_path("core", None, Core::ATTRIBUTES_FILE.name)
163-
.transpose()?
164-
{
165-
Some(attributes) => Some(attributes.into_owned()),
166-
None => me.xdg_config_path("attributes").ok().flatten(),
167-
};
168-
// TODO: implement gix_attributes::Search::from_git_dir(), similar to what's done for `Ignore`.
169-
Ok(Default::default())
170-
}
171-
172159
let thread_limit = self.apply_leniency(
173160
self.resolved
174161
.integer_filter_by_key("checkout.workers", &mut self.filter_config_section.clone())
175162
.map(|value| Checkout::WORKERS.try_from_workers(value)),
176163
)?;
164+
let capabilities = gix_worktree::fs::Capabilities {
165+
precompose_unicode: boolean(self, "core.precomposeUnicode", &Core::PRECOMPOSE_UNICODE, false)?,
166+
ignore_case: boolean(self, "core.ignoreCase", &Core::IGNORE_CASE, false)?,
167+
executable_bit: boolean(self, "core.fileMode", &Core::FILE_MODE, true)?,
168+
symlink: boolean(self, "core.symlinks", &Core::SYMLINKS, true)?,
169+
};
170+
let case = if capabilities.ignore_case {
171+
Case::Fold
172+
} else {
173+
Case::Sensitive
174+
};
177175
Ok(gix_worktree::index::checkout::Options {
178-
fs: gix_worktree::fs::Capabilities {
179-
precompose_unicode: boolean(self, "core.precomposeUnicode", &Core::PRECOMPOSE_UNICODE, false)?,
180-
ignore_case: boolean(self, "core.ignoreCase", &Core::IGNORE_CASE, false)?,
181-
executable_bit: boolean(self, "core.fileMode", &Core::FILE_MODE, true)?,
182-
symlink: boolean(self, "core.symlinks", &Core::SYMLINKS, true)?,
183-
},
176+
attributes: self.assemble_attribute_globals(
177+
git_dir,
178+
case,
179+
gix_worktree::fs::cache::state::attributes::Source::AttributeListThenWorktree,
180+
self.attributes,
181+
)?,
182+
fs: capabilities,
184183
thread_limit,
185184
destination_is_initially_empty: false,
186185
overwrite_existing: false,
@@ -193,23 +192,65 @@ impl Cache {
193192
.map(|v| Core::CHECK_STAT.try_into_checkstat(v)),
194193
)?
195194
.unwrap_or(true),
196-
attribute_globals: assemble_attribute_globals(self, git_dir)?,
197195
})
198196
}
197+
198+
// TODO: at least one test, maybe related to core.attributesFile configuration.
199+
fn assemble_attribute_globals(
200+
&self,
201+
git_dir: &std::path::Path,
202+
case: gix_glob::pattern::Case,
203+
source: gix_worktree::fs::cache::state::attributes::Source,
204+
attributes: crate::permissions::Attributes,
205+
) -> Result<gix_worktree::fs::cache::state::Attributes, config::attribute_stack::Error> {
206+
let configured_or_user_attributes = match self
207+
.trusted_file_path("core", None, Core::ATTRIBUTES_FILE.name)
208+
.transpose()?
209+
{
210+
Some(attributes) => Some(attributes),
211+
None => {
212+
if attributes.git {
213+
self.xdg_config_path("attributes").ok().flatten().map(Cow::Owned)
214+
} else {
215+
None
216+
}
217+
}
218+
};
219+
let attribute_files = [gix_attributes::Source::GitInstallation, gix_attributes::Source::System]
220+
.into_iter()
221+
.filter(|source| match source {
222+
Source::GitInstallation => attributes.git_binary,
223+
Source::System => attributes.system,
224+
Source::Git | Source::Local => unreachable!("we don't offer turning this off right now"),
225+
})
226+
.filter_map(|source| source.storage_location(&mut Self::make_source_env(self.environment)))
227+
.chain(configured_or_user_attributes);
228+
let info_attributes_path = git_dir.join("info").join("attributes");
229+
let mut buf = Vec::new();
230+
let mut collection = gix_attributes::search::MetadataCollection::default();
231+
Ok(gix_worktree::fs::cache::state::Attributes::new(
232+
gix_attributes::Search::new_globals(attribute_files, &mut buf, &mut collection)?,
233+
Some(info_attributes_path),
234+
case,
235+
source,
236+
collection,
237+
))
238+
}
239+
199240
pub(crate) fn xdg_config_path(
200241
&self,
201242
resource_file_name: &str,
202243
) -> Result<Option<PathBuf>, gix_sec::permission::Error<PathBuf>> {
203244
std::env::var_os("XDG_CONFIG_HOME")
204-
.map(|path| (PathBuf::from(path), &self.xdg_config_home_env))
245+
.map(|path| (PathBuf::from(path), &self.environment.xdg_config_home))
205246
.or_else(|| {
206247
gix_path::env::home_dir().map(|mut p| {
207248
(
208249
{
209250
p.push(".config");
210251
p
211252
},
212-
&self.home_env,
253+
&self.environment.home,
213254
)
214255
})
215256
})
@@ -225,6 +266,6 @@ impl Cache {
225266
/// We never fail for here even if the permission is set to deny as we `gix-config` will fail later
226267
/// if it actually wants to use the home directory - we don't want to fail prematurely.
227268
pub(crate) fn home_dir(&self) -> Option<PathBuf> {
228-
gix_path::env::home_dir().and_then(|path| self.home_env.check_opt(path))
269+
gix_path::env::home_dir().and_then(|path| self.environment.home.check_opt(path))
229270
}
230271
}

gix/src/config/cache/init.rs

+33-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(clippy::result_large_err)]
22
use std::borrow::Cow;
3+
use std::ffi::OsString;
34

45
use gix_sec::Permission;
56

@@ -32,15 +33,16 @@ impl Cache {
3233
filter_config_section: fn(&gix_config::file::Metadata) -> bool,
3334
git_install_dir: Option<&std::path::Path>,
3435
home: Option<&std::path::Path>,
35-
repository::permissions::Environment {
36+
environment @ repository::permissions::Environment {
3637
git_prefix,
37-
home: home_env,
38-
xdg_config_home: xdg_config_home_env,
3938
ssh_prefix: _,
39+
xdg_config_home: _,
40+
home: _,
4041
http_transport,
4142
identity,
4243
objects,
4344
}: repository::permissions::Environment,
45+
attributes: repository::permissions::Attributes,
4446
repository::permissions::Config {
4547
git_binary: use_installation,
4648
system: use_system,
@@ -69,8 +71,6 @@ impl Cache {
6971
};
7072

7173
let config = {
72-
let home_env = &home_env;
73-
let xdg_config_home_env = &xdg_config_home_env;
7474
let git_prefix = &git_prefix;
7575
let metas = [
7676
gix_config::source::Kind::GitInstallation,
@@ -88,15 +88,7 @@ impl Cache {
8888
_ => {}
8989
}
9090
source
91-
.storage_location(&mut |name| {
92-
match name {
93-
git_ if git_.starts_with("GIT_") => Some(git_prefix),
94-
"XDG_CONFIG_HOME" => Some(xdg_config_home_env),
95-
"HOME" => Some(home_env),
96-
_ => None,
97-
}
98-
.and_then(|perm| perm.check_opt(name).and_then(gix_path::env::var))
99-
})
91+
.storage_location(&mut Self::make_source_env(environment))
10092
.map(|p| (source, p.into_owned()))
10193
})
10294
.map(|(source, path)| gix_config::file::Metadata {
@@ -175,9 +167,9 @@ impl Cache {
175167
ignore_case,
176168
hex_len,
177169
filter_config_section,
178-
xdg_config_home_env,
179-
home_env,
170+
environment,
180171
lenient_config,
172+
attributes,
181173
user_agent: Default::default(),
182174
personas: Default::default(),
183175
url_rewrite: Default::default(),
@@ -240,6 +232,31 @@ impl Cache {
240232

241233
Ok(())
242234
}
235+
236+
pub(crate) fn make_source_env(
237+
crate::permissions::Environment {
238+
xdg_config_home,
239+
git_prefix,
240+
home,
241+
..
242+
}: crate::permissions::Environment,
243+
) -> impl FnMut(&str) -> Option<OsString> {
244+
move |name| {
245+
match name {
246+
git_ if git_.starts_with("GIT_") => Some(git_prefix),
247+
"XDG_CONFIG_HOME" => Some(xdg_config_home),
248+
"HOME" => {
249+
return if home.is_allowed() {
250+
gix_path::env::home_dir().map(Into::into)
251+
} else {
252+
None
253+
}
254+
}
255+
_ => None,
256+
}
257+
.and_then(|perm| perm.check_opt(name).and_then(gix_path::env::var))
258+
}
259+
}
243260
}
244261

245262
impl crate::Repository {

gix/src/config/mod.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ pub mod checkout_options {
113113
ConfigBoolean(#[from] super::boolean::Error),
114114
#[error(transparent)]
115115
CheckoutWorkers(#[from] super::checkout::workers::Error),
116+
#[error(transparent)]
117+
Attributes(#[from] super::attribute_stack::Error),
118+
}
119+
}
120+
121+
///
122+
pub mod attribute_stack {
123+
/// The error produced when setting up the attribute stack to query `gitattributes`.
124+
#[derive(Debug, thiserror::Error)]
125+
#[allow(missing_docs)]
126+
pub enum Error {
127+
#[error("An attribute file could not be read")]
128+
Io(#[from] std::io::Error),
116129
#[error("Failed to interpolate the attribute file configured at `core.attributesFile`")]
117130
AttributesFileInterpolation(#[from] gix_config::path::interpolate::Error),
118131
}
@@ -449,9 +462,7 @@ pub(crate) struct Cache {
449462
/// If true, we should default what's possible if something is misconfigured, on case by case basis, to be more resilient.
450463
/// Also available in options! Keep in sync!
451464
pub lenient_config: bool,
452-
/// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable.
453-
xdg_config_home_env: gix_sec::Permission,
454-
/// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable.
455-
home_env: gix_sec::Permission,
465+
attributes: crate::permissions::Attributes,
466+
environment: crate::permissions::Environment,
456467
// TODO: make core.precomposeUnicode available as well.
457468
}

gix/src/open/repository.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,12 @@ impl ThreadSafeRepository {
146146
lenient_config,
147147
bail_if_untrusted,
148148
open_path_as_is: _,
149-
permissions: Permissions { ref env, config },
149+
permissions:
150+
Permissions {
151+
ref env,
152+
config,
153+
attributes,
154+
},
150155
ref api_config_overrides,
151156
ref cli_config_overrides,
152157
ref current_dir,
@@ -190,7 +195,8 @@ impl ThreadSafeRepository {
190195
filter_config_section,
191196
git_install_dir.as_deref(),
192197
home.as_deref(),
193-
env.clone(),
198+
*env,
199+
attributes,
194200
config,
195201
lenient_config,
196202
api_config_overrides,

gix/src/permissions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
pub use crate::repository::permissions::{Config, Environment};
1+
pub use crate::repository::permissions::{Attributes, Config, Environment};

0 commit comments

Comments
 (0)