]> git.proxmox.com Git - rustc.git/blob - vendor/gix/src/config/cache/access.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / vendor / gix / src / config / cache / access.rs
1 #![allow(clippy::result_large_err)]
2 use std::{borrow::Cow, path::PathBuf, time::Duration};
3
4 use gix_lock::acquire::Fail;
5
6 use crate::{
7 bstr::BStr,
8 config,
9 config::{
10 boolean,
11 cache::util::{ApplyLeniency, ApplyLeniencyDefaultValue},
12 tree::{Core, Key},
13 Cache,
14 },
15 remote,
16 repository::identity,
17 };
18
19 /// Access
20 impl Cache {
21 #[cfg(feature = "blob-diff")]
22 pub(crate) fn diff_algorithm(&self) -> Result<gix_diff::blob::Algorithm, config::diff::algorithm::Error> {
23 use crate::config::{cache::util::ApplyLeniencyDefault, diff::algorithm::Error};
24 self.diff_algorithm
25 .get_or_try_init(|| {
26 let name = self
27 .resolved
28 .string("diff", None, "algorithm")
29 .unwrap_or_else(|| Cow::Borrowed("myers".into()));
30 config::tree::Diff::ALGORITHM
31 .try_into_algorithm(name)
32 .or_else(|err| match err {
33 Error::Unimplemented { .. } if self.lenient_config => Ok(gix_diff::blob::Algorithm::Histogram),
34 err => Err(err),
35 })
36 .with_lenient_default(self.lenient_config)
37 })
38 .copied()
39 }
40
41 #[cfg(feature = "blob-diff")]
42 pub(crate) fn diff_drivers(&self) -> Result<Vec<gix_diff::blob::Driver>, config::diff::drivers::Error> {
43 use crate::config::cache::util::ApplyLeniencyDefault;
44 let mut out = Vec::<gix_diff::blob::Driver>::new();
45 for section in self
46 .resolved
47 .sections_by_name("diff")
48 .into_iter()
49 .flatten()
50 .filter(|s| (self.filter_config_section)(s.meta()))
51 {
52 let Some(name) = section.header().subsection_name().filter(|n| !n.is_empty()) else {
53 continue;
54 };
55
56 let driver = match out.iter_mut().find(|d| d.name == name) {
57 Some(existing) => existing,
58 None => {
59 out.push(gix_diff::blob::Driver {
60 name: name.into(),
61 ..Default::default()
62 });
63 out.last_mut().expect("just pushed")
64 }
65 };
66
67 if let Some(binary) = section.value_implicit("binary") {
68 driver.is_binary = config::tree::Diff::DRIVER_BINARY
69 .try_into_binary(binary)
70 .with_leniency(self.lenient_config)
71 .map_err(|err| config::diff::drivers::Error {
72 name: driver.name.clone(),
73 attribute: "binary",
74 source: Box::new(err),
75 })?;
76 }
77 if let Some(command) = section.value(config::tree::Diff::DRIVER_COMMAND.name) {
78 driver.command = command.into_owned().into();
79 }
80 if let Some(textconv) = section.value(config::tree::Diff::DRIVER_TEXTCONV.name) {
81 driver.binary_to_text_command = textconv.into_owned().into();
82 }
83 if let Some(algorithm) = section.value("algorithm") {
84 driver.algorithm = config::tree::Diff::DRIVER_ALGORITHM
85 .try_into_algorithm(algorithm)
86 .or_else(|err| match err {
87 config::diff::algorithm::Error::Unimplemented { .. } if self.lenient_config => {
88 Ok(gix_diff::blob::Algorithm::Histogram)
89 }
90 err => Err(err),
91 })
92 .with_lenient_default(self.lenient_config)
93 .map_err(|err| config::diff::drivers::Error {
94 name: driver.name.clone(),
95 attribute: "algorithm",
96 source: Box::new(err),
97 })?
98 .into();
99 }
100 }
101 Ok(out)
102 }
103
104 #[cfg(feature = "blob-diff")]
105 pub(crate) fn diff_pipeline_options(
106 &self,
107 ) -> Result<gix_diff::blob::pipeline::Options, config::diff::pipeline_options::Error> {
108 Ok(gix_diff::blob::pipeline::Options {
109 large_file_threshold_bytes: self.big_file_threshold()?,
110 fs: self.fs_capabilities()?,
111 })
112 }
113
114 #[cfg(feature = "blob-diff")]
115 pub(crate) fn diff_renames(&self) -> Result<Option<crate::diff::Rewrites>, crate::diff::new_rewrites::Error> {
116 self.diff_renames
117 .get_or_try_init(|| crate::diff::new_rewrites(&self.resolved, self.lenient_config))
118 .copied()
119 }
120
121 #[cfg(feature = "blob-diff")]
122 pub(crate) fn big_file_threshold(&self) -> Result<u64, config::unsigned_integer::Error> {
123 Ok(self
124 .resolved
125 .integer_by_key("core.bigFileThreshold")
126 .map(|number| Core::BIG_FILE_THRESHOLD.try_into_u64(number))
127 .transpose()
128 .with_leniency(self.lenient_config)?
129 .unwrap_or(512 * 1024 * 1024))
130 }
131
132 /// Returns a user agent for use with servers.
133 #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
134 pub(crate) fn user_agent_tuple(&self) -> (&'static str, Option<Cow<'static, str>>) {
135 use config::tree::Gitoxide;
136 let agent = self
137 .user_agent
138 .get_or_init(|| {
139 self.resolved
140 .string_by_key(Gitoxide::USER_AGENT.logical_name().as_str())
141 .map_or_else(|| crate::env::agent().into(), |s| s.to_string())
142 })
143 .to_owned();
144 ("agent", Some(gix_protocol::agent(agent).into()))
145 }
146
147 /// Return `true` if packet-tracing is enabled. Lenient and defaults to `false`.
148 #[cfg(any(feature = "async-network-client", feature = "blocking-network-client"))]
149 pub(crate) fn trace_packet(&self) -> bool {
150 use config::tree::Gitoxide;
151
152 use crate::config::tree::Section;
153 self.resolved
154 .boolean(Gitoxide.name(), None, Gitoxide::TRACE_PACKET.name())
155 .and_then(Result::ok)
156 .unwrap_or_default()
157 }
158
159 pub(crate) fn personas(&self) -> &identity::Personas {
160 self.personas
161 .get_or_init(|| identity::Personas::from_config_and_env(&self.resolved))
162 }
163
164 pub(crate) fn url_rewrite(&self) -> &remote::url::Rewrite {
165 self.url_rewrite
166 .get_or_init(|| remote::url::Rewrite::from_config(&self.resolved, self.filter_config_section))
167 }
168
169 #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
170 pub(crate) fn url_scheme(&self) -> Result<&remote::url::SchemePermission, config::protocol::allow::Error> {
171 self.url_scheme
172 .get_or_try_init(|| remote::url::SchemePermission::from_config(&self.resolved, self.filter_config_section))
173 }
174
175 pub(crate) fn may_use_commit_graph(&self) -> Result<bool, config::boolean::Error> {
176 const DEFAULT: bool = true;
177 self.resolved
178 .boolean_by_key("core.commitGraph")
179 .map_or(Ok(DEFAULT), |res| {
180 Core::COMMIT_GRAPH
181 .enrich_error(res)
182 .with_lenient_default_value(self.lenient_config, DEFAULT)
183 })
184 }
185
186 /// Returns (file-timeout, pack-refs timeout)
187 pub(crate) fn lock_timeout(
188 &self,
189 ) -> Result<(gix_lock::acquire::Fail, gix_lock::acquire::Fail), config::lock_timeout::Error> {
190 let mut out: [gix_lock::acquire::Fail; 2] = Default::default();
191 for (idx, (key, default_ms)) in [(&Core::FILES_REF_LOCK_TIMEOUT, 100), (&Core::PACKED_REFS_TIMEOUT, 1000)]
192 .into_iter()
193 .enumerate()
194 {
195 out[idx] = self
196 .resolved
197 .integer_filter("core", None, key.name, &mut self.filter_config_section.clone())
198 .map(|res| key.try_into_lock_timeout(res))
199 .transpose()
200 .with_leniency(self.lenient_config)?
201 .unwrap_or_else(|| Fail::AfterDurationWithBackoff(Duration::from_millis(default_ms)));
202 }
203 Ok((out[0], out[1]))
204 }
205
206 /// The path to the user-level excludes file to ignore certain files in the worktree.
207 #[cfg(feature = "excludes")]
208 pub(crate) fn excludes_file(&self) -> Option<Result<PathBuf, gix_config::path::interpolate::Error>> {
209 self.trusted_file_path("core", None, Core::EXCLUDES_FILE.name)?
210 .map(std::borrow::Cow::into_owned)
211 .into()
212 }
213
214 /// A helper to obtain a file from trusted configuration at `section_name`, `subsection_name`, and `key`, which is interpolated
215 /// if present.
216 pub(crate) fn trusted_file_path(
217 &self,
218 section_name: impl AsRef<str>,
219 subsection_name: Option<&BStr>,
220 key: impl AsRef<str>,
221 ) -> Option<Result<Cow<'_, std::path::Path>, gix_config::path::interpolate::Error>> {
222 let path = self.resolved.path_filter(
223 section_name,
224 subsection_name,
225 key,
226 &mut self.filter_config_section.clone(),
227 )?;
228
229 let install_dir = crate::path::install_dir().ok();
230 let home = self.home_dir();
231 let ctx = config::cache::interpolate_context(install_dir.as_deref(), home.as_deref());
232 Some(path.interpolate(ctx))
233 }
234
235 pub(crate) fn apply_leniency<T, E>(&self, res: Option<Result<T, E>>) -> Result<Option<T>, E> {
236 res.transpose().with_leniency(self.lenient_config)
237 }
238
239 pub(crate) fn fs_capabilities(&self) -> Result<gix_fs::Capabilities, boolean::Error> {
240 Ok(gix_fs::Capabilities {
241 precompose_unicode: boolean(self, "core.precomposeUnicode", &Core::PRECOMPOSE_UNICODE, false)?,
242 ignore_case: boolean(self, "core.ignoreCase", &Core::IGNORE_CASE, false)?,
243 executable_bit: boolean(self, "core.fileMode", &Core::FILE_MODE, true)?,
244 symlink: boolean(self, "core.symlinks", &Core::SYMLINKS, true)?,
245 })
246 }
247
248 #[cfg(feature = "index")]
249 pub(crate) fn stat_options(&self) -> Result<gix_index::entry::stat::Options, config::stat_options::Error> {
250 use crate::config::tree::gitoxide;
251 Ok(gix_index::entry::stat::Options {
252 trust_ctime: boolean(self, "core.trustCTime", &Core::TRUST_C_TIME, true)?,
253 use_nsec: boolean(self, "gitoxide.core.useNsec", &gitoxide::Core::USE_NSEC, false)?,
254 use_stdev: boolean(self, "gitoxide.core.useStdev", &gitoxide::Core::USE_STDEV, false)?,
255 check_stat: self
256 .apply_leniency(
257 self.resolved
258 .string("core", None, "checkStat")
259 .map(|v| Core::CHECK_STAT.try_into_checkstat(v)),
260 )?
261 .unwrap_or(true),
262 })
263 }
264
265 /// Collect everything needed to checkout files into a worktree.
266 /// Note that some of the options being returned will be defaulted so safe settings, the caller might have to override them
267 /// depending on the use-case.
268 #[cfg(feature = "worktree-mutation")]
269 pub(crate) fn checkout_options(
270 &self,
271 repo: &crate::Repository,
272 attributes_source: gix_worktree::stack::state::attributes::Source,
273 ) -> Result<gix_worktree_state::checkout::Options, config::checkout_options::Error> {
274 use crate::config::tree::gitoxide;
275 let git_dir = repo.git_dir();
276 let thread_limit = self.apply_leniency(
277 self.resolved
278 .integer_filter_by_key("checkout.workers", &mut self.filter_config_section.clone())
279 .map(|value| crate::config::tree::Checkout::WORKERS.try_from_workers(value)),
280 )?;
281 let capabilities = self.fs_capabilities()?;
282 let filters = {
283 let mut filters =
284 gix_filter::Pipeline::new(repo.command_context()?, crate::filter::Pipeline::options(repo)?);
285 if let Ok(mut head) = repo.head() {
286 let ctx = filters.driver_context_mut();
287 ctx.ref_name = head.referent_name().map(|name| name.as_bstr().to_owned());
288 ctx.treeish = head.peel_to_commit_in_place().ok().map(|commit| commit.id);
289 }
290 filters
291 };
292 let filter_process_delay = if boolean(
293 self,
294 "gitoxide.core.filterProcessDelay",
295 &gitoxide::Core::FILTER_PROCESS_DELAY,
296 true,
297 )? {
298 gix_filter::driver::apply::Delay::Allow
299 } else {
300 gix_filter::driver::apply::Delay::Forbid
301 };
302 Ok(gix_worktree_state::checkout::Options {
303 filter_process_delay,
304 filters,
305 attributes: self
306 .assemble_attribute_globals(git_dir, attributes_source, self.attributes)?
307 .0,
308 fs: capabilities,
309 thread_limit,
310 destination_is_initially_empty: false,
311 overwrite_existing: false,
312 keep_going: false,
313 stat_options: self.stat_options().map_err(|err| match err {
314 config::stat_options::Error::ConfigCheckStat(err) => {
315 config::checkout_options::Error::ConfigCheckStat(err)
316 }
317 config::stat_options::Error::ConfigBoolean(err) => config::checkout_options::Error::ConfigBoolean(err),
318 })?,
319 })
320 }
321
322 #[cfg(feature = "excludes")]
323 pub(crate) fn assemble_exclude_globals(
324 &self,
325 git_dir: &std::path::Path,
326 overrides: Option<gix_ignore::Search>,
327 source: gix_worktree::stack::state::ignore::Source,
328 buf: &mut Vec<u8>,
329 ) -> Result<gix_worktree::stack::state::Ignore, config::exclude_stack::Error> {
330 let excludes_file = match self.excludes_file().transpose()? {
331 Some(user_path) => Some(user_path),
332 None => self.xdg_config_path("ignore")?,
333 };
334 Ok(gix_worktree::stack::state::Ignore::new(
335 overrides.unwrap_or_default(),
336 gix_ignore::Search::from_git_dir(git_dir, excludes_file, buf)?,
337 None,
338 source,
339 ))
340 }
341 // TODO: at least one test, maybe related to core.attributesFile configuration.
342 #[cfg(feature = "attributes")]
343 pub(crate) fn assemble_attribute_globals(
344 &self,
345 git_dir: &std::path::Path,
346 source: gix_worktree::stack::state::attributes::Source,
347 attributes: crate::open::permissions::Attributes,
348 ) -> Result<(gix_worktree::stack::state::Attributes, Vec<u8>), config::attribute_stack::Error> {
349 use gix_attributes::Source;
350 let configured_or_user_attributes = match self
351 .trusted_file_path("core", None, Core::ATTRIBUTES_FILE.name)
352 .transpose()?
353 {
354 Some(attributes) => Some(attributes),
355 None => {
356 if attributes.git {
357 self.xdg_config_path("attributes").ok().flatten().map(Cow::Owned)
358 } else {
359 None
360 }
361 }
362 };
363 let attribute_files = [gix_attributes::Source::GitInstallation, gix_attributes::Source::System]
364 .into_iter()
365 .filter(|source| match source {
366 Source::GitInstallation => attributes.git_binary,
367 Source::System => attributes.system,
368 Source::Git | Source::Local => unreachable!("we don't offer turning this off right now"),
369 })
370 .filter_map(|source| source.storage_location(&mut Self::make_source_env(self.environment)))
371 .chain(configured_or_user_attributes);
372 let info_attributes_path = git_dir.join("info").join("attributes");
373 let mut buf = Vec::new();
374 let mut collection = gix_attributes::search::MetadataCollection::default();
375 let state = gix_worktree::stack::state::Attributes::new(
376 gix_attributes::Search::new_globals(attribute_files, &mut buf, &mut collection)?,
377 Some(info_attributes_path),
378 source,
379 collection,
380 );
381 Ok((state, buf))
382 }
383
384 #[cfg(feature = "attributes")]
385 pub(crate) fn pathspec_defaults(
386 &self,
387 ) -> Result<gix_pathspec::Defaults, gix_pathspec::defaults::from_environment::Error> {
388 use crate::config::tree::gitoxide;
389 let res = gix_pathspec::Defaults::from_environment(&mut |name| {
390 let key = [
391 &gitoxide::Pathspec::ICASE,
392 &gitoxide::Pathspec::GLOB,
393 &gitoxide::Pathspec::NOGLOB,
394 &gitoxide::Pathspec::LITERAL,
395 ]
396 .iter()
397 .find(|key| key.environment_override().expect("set") == name)
398 .expect("we must know all possible input variable names");
399
400 let val = self
401 .resolved
402 .string("gitoxide", Some("pathspec".into()), key.name())
403 .map(gix_path::from_bstr)?;
404 Some(val.into_owned().into())
405 });
406 if res.is_err() && self.lenient_config {
407 Ok(gix_pathspec::Defaults::default())
408 } else {
409 res
410 }
411 }
412
413 #[cfg(any(feature = "attributes", feature = "excludes"))]
414 pub(crate) fn xdg_config_path(
415 &self,
416 resource_file_name: &str,
417 ) -> Result<Option<PathBuf>, gix_sec::permission::Error<PathBuf>> {
418 std::env::var_os("XDG_CONFIG_HOME")
419 .map(|path| (PathBuf::from(path), &self.environment.xdg_config_home))
420 .or_else(|| {
421 gix_path::env::home_dir().map(|mut p| {
422 (
423 {
424 p.push(".config");
425 p
426 },
427 &self.environment.home,
428 )
429 })
430 })
431 .and_then(|(base, permission)| {
432 let resource = base.join("git").join(resource_file_name);
433 permission.check(resource).transpose()
434 })
435 .transpose()
436 }
437
438 /// Return the home directory if we are allowed to read it and if it is set in the environment.
439 ///
440 /// We never fail for here even if the permission is set to deny as we `gix-config` will fail later
441 /// if it actually wants to use the home directory - we don't want to fail prematurely.
442 pub(crate) fn home_dir(&self) -> Option<PathBuf> {
443 gix_path::env::home_dir().and_then(|path| self.environment.home.check_opt(path))
444 }
445 }
446
447 fn boolean(
448 me: &Cache,
449 full_key: &str,
450 key: &'static config::tree::keys::Boolean,
451 default: bool,
452 ) -> Result<bool, boolean::Error> {
453 debug_assert_eq!(
454 full_key,
455 key.logical_name(),
456 "BUG: key name and hardcoded name must match"
457 );
458 Ok(me
459 .apply_leniency(me.resolved.boolean_by_key(full_key).map(|v| key.enrich_error(v)))?
460 .unwrap_or(default))
461 }