1 #![allow(clippy::result_large_err)]
2 use std
::{borrow::Cow, path::PathBuf, time::Duration}
;
4 use gix_lock
::acquire
::Fail
;
11 cache
::util
::{ApplyLeniency, ApplyLeniencyDefaultValue}
,
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}
;
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
),
36 .with_lenient_default(self.lenient_config
)
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();
47 .sections_by_name("diff")
50 .filter(|s
| (self.filter_config_section
)(s
.meta()))
52 let Some(name
) = section
.header().subsection_name().filter(|n
| !n
.is_empty()) else {
56 let driver
= match out
.iter_mut().find(|d
| d
.name
== name
) {
57 Some(existing
) => existing
,
59 out
.push(gix_diff
::blob
::Driver
{
63 out
.last_mut().expect("just pushed")
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(),
74 source
: Box
::new(err
),
77 if let Some(command
) = section
.value(config
::tree
::Diff
::DRIVER_COMMAND
.name
) {
78 driver
.command
= command
.into_owned().into();
80 if let Some(textconv
) = section
.value(config
::tree
::Diff
::DRIVER_TEXTCONV
.name
) {
81 driver
.binary_to_text_command
= textconv
.into_owned().into();
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
)
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
),
104 #[cfg(feature = "blob-diff")]
105 pub(crate) fn diff_pipeline_options(
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()?
,
114 #[cfg(feature = "blob-diff")]
115 pub(crate) fn diff_renames(&self) -> Result
<Option
<crate::diff
::Rewrites
>, crate::diff
::new_rewrites
::Error
> {
117 .get_or_try_init(|| crate::diff
::new_rewrites(&self.resolved
, self.lenient_config
))
121 #[cfg(feature = "blob-diff")]
122 pub(crate) fn big_file_threshold(&self) -> Result
<u64, config
::unsigned_integer
::Error
> {
125 .integer_by_key("core.bigFileThreshold")
126 .map(|number
| Core
::BIG_FILE_THRESHOLD
.try_into_u64(number
))
128 .with_leniency(self.lenient_config
)?
129 .unwrap_or(512 * 1024 * 1024))
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
;
140 .string_by_key(Gitoxide
::USER_AGENT
.logical_name().as_str())
141 .map_or_else(|| crate::env
::agent().into(), |s
| s
.to_string())
144 ("agent", Some(gix_protocol
::agent(agent
).into()))
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
;
152 use crate::config
::tree
::Section
;
154 .boolean(Gitoxide
.name(), None
, Gitoxide
::TRACE_PACKET
.name())
155 .and_then(Result
::ok
)
159 pub(crate) fn personas(&self) -> &identity
::Personas
{
161 .get_or_init(|| identity
::Personas
::from_config_and_env(&self.resolved
))
164 pub(crate) fn url_rewrite(&self) -> &remote
::url
::Rewrite
{
166 .get_or_init(|| remote
::url
::Rewrite
::from_config(&self.resolved
, self.filter_config_section
))
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
> {
172 .get_or_try_init(|| remote
::url
::SchemePermission
::from_config(&self.resolved
, self.filter_config_section
))
175 pub(crate) fn may_use_commit_graph(&self) -> Result
<bool
, config
::boolean
::Error
> {
176 const DEFAULT
: bool
= true;
178 .boolean_by_key("core.commitGraph")
179 .map_or(Ok(DEFAULT
), |res
| {
182 .with_lenient_default_value(self.lenient_config
, DEFAULT
)
186 /// Returns (file-timeout, pack-refs timeout)
187 pub(crate) fn lock_timeout(
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)]
197 .integer_filter("core", None
, key
.name
, &mut self.filter_config_section
.clone())
198 .map(|res
| key
.try_into_lock_timeout(res
))
200 .with_leniency(self.lenient_config
)?
201 .unwrap_or_else(|| Fail
::AfterDurationWithBackoff(Duration
::from_millis(default_ms
)));
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
)
214 /// A helper to obtain a file from trusted configuration at `section_name`, `subsection_name`, and `key`, which is interpolated
216 pub(crate) fn trusted_file_path(
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(
226 &mut self.filter_config_section
.clone(),
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
))
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
)
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)?
,
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)?
,
258 .string("core", None
, "checkStat")
259 .map(|v
| Core
::CHECK_STAT
.try_into_checkstat(v
)),
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(
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(
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
)),
281 let capabilities
= self.fs_capabilities()?
;
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
);
292 let filter_process_delay
= if boolean(
294 "gitoxide.core.filterProcessDelay",
295 &gitoxide
::Core
::FILTER_PROCESS_DELAY
,
298 gix_filter
::driver
::apply
::Delay
::Allow
300 gix_filter
::driver
::apply
::Delay
::Forbid
302 Ok(gix_worktree_state
::checkout
::Options
{
303 filter_process_delay
,
306 .assemble_attribute_globals(git_dir
, attributes_source
, self.attributes
)?
310 destination_is_initially_empty
: false,
311 overwrite_existing
: 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
)
317 config
::stat_options
::Error
::ConfigBoolean(err
) => config
::checkout_options
::Error
::ConfigBoolean(err
),
322 #[cfg(feature = "excludes")]
323 pub(crate) fn assemble_exclude_globals(
325 git_dir
: &std
::path
::Path
,
326 overrides
: Option
<gix_ignore
::Search
>,
327 source
: gix_worktree
::stack
::state
::ignore
::Source
,
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")?
,
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
)?
,
341 // TODO: at least one test, maybe related to core.attributesFile configuration.
342 #[cfg(feature = "attributes")]
343 pub(crate) fn assemble_attribute_globals(
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
)
354 Some(attributes
) => Some(attributes
),
357 self.xdg_config_path("attributes").ok().flatten().map(Cow
::Owned
)
363 let attribute_files
= [gix_attributes
::Source
::GitInstallation
, gix_attributes
::Source
::System
]
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"),
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
),
384 #[cfg(feature = "attributes")]
385 pub(crate) fn pathspec_defaults(
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
| {
391 &gitoxide
::Pathspec
::ICASE
,
392 &gitoxide
::Pathspec
::GLOB
,
393 &gitoxide
::Pathspec
::NOGLOB
,
394 &gitoxide
::Pathspec
::LITERAL
,
397 .find(|key
| key
.environment_override().expect("set") == name
)
398 .expect("we must know all possible input variable names");
402 .string("gitoxide", Some("pathspec".into()), key
.name())
403 .map(gix_path
::from_bstr
)?
;
404 Some(val
.into_owned().into())
406 if res
.is_err() && self.lenient_config
{
407 Ok(gix_pathspec
::Defaults
::default())
413 #[cfg(any(feature = "attributes", feature = "excludes"))]
414 pub(crate) fn xdg_config_path(
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
))
421 gix_path
::env
::home_dir().map(|mut p
| {
427 &self.environment
.home
,
431 .and_then(|(base
, permission
)| {
432 let resource
= base
.join("git").join(resource_file_name
);
433 permission
.check(resource
).transpose()
438 /// Return the home directory if we are allowed to read it and if it is set in the environment.
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
))
450 key
: &'
static config
::tree
::keys
::Boolean
,
452 ) -> Result
<bool
, boolean
::Error
> {
456 "BUG: key name and hardcoded name must match"
459 .apply_leniency(me
.resolved
.boolean_by_key(full_key
).map(|v
| key
.enrich_error(v
)))?