use std::{borrow::Cow, convert::TryInto, path::PathBuf, time::Duration}; use git_lock::acquire::Fail; use crate::{ config::{cache::util::check_lenient_default, checkout_options, Cache}, remote, repository::identity,}; /// Accessimpl Cache { pub(crate) fn diff_algorithm(&self) -> Result<git_diff::text::Algorithm, crate::config::diff::algorithm::Error> { use crate::config::diff::algorithm::Error; self.diff_algorithm .get_or_try_init(|| { let res = { let name = self .resolved .string("diff", None, "algorithm") .unwrap_or_else(|| Cow::Borrowed("myers".into())); if name.eq_ignore_ascii_case(b"myers") || name.eq_ignore_ascii_case(b"default") { Ok(git_diff::text::Algorithm::Myers) } else if name.eq_ignore_ascii_case(b"minimal") { Ok(git_diff::text::Algorithm::MyersMinimal) } else if name.eq_ignore_ascii_case(b"histogram") { Ok(git_diff::text::Algorithm::Histogram) } else if name.eq_ignore_ascii_case(b"patience") { if self.lenient_config { Ok(git_diff::text::Algorithm::Histogram) } else { Err(Error::Unimplemented { name: name.into_owned(), }) } } else { Err(Error::Unknown { name: name.into_owned(), }) } }; check_lenient_default(res, self.lenient_config, || git_diff::text::Algorithm::Myers) }) .copied() } pub(crate) fn personas(&self) -> &identity::Personas { self.personas .get_or_init(|| identity::Personas::from_config_and_env(&self.resolved, self.git_prefix)) } pub(crate) fn url_rewrite(&self) -> &remote::url::Rewrite { self.url_rewrite .get_or_init(|| remote::url::Rewrite::from_config(&self.resolved, self.filter_config_section)) } #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] pub(crate) fn url_scheme( &self, ) -> Result<&remote::url::SchemePermission, remote::url::scheme_permission::init::Error> { self.url_scheme.get_or_try_init(|| { remote::url::SchemePermission::from_config(&self.resolved, self.git_prefix, self.filter_config_section) }) } /// Returns (file-timeout, pack-refs timeout) pub(crate) fn lock_timeout( &self, ) -> Result<(git_lock::acquire::Fail, git_lock::acquire::Fail), git_config::value::Error> { enum Kind { RefFiles, RefPackFile, } let mut out: [git_lock::acquire::Fail; 2] = Default::default(); for (idx, kind) in [Kind::RefFiles, Kind::RefPackFile].iter().enumerate() { let (key, default_ms) = match kind { Kind::RefFiles => ("filesRefLockTimeout", 100), Kind::RefPackFile => ("packedRefsTimeout", 1000), }; let mk_default = || Fail::AfterDurationWithBackoff(Duration::from_millis(default_ms)); let mut fnp = self.filter_config_section; let lock_mode = match self.resolved.integer_filter("core", None, key, &mut fnp) { Some(Ok(val)) if val < 0 => Fail::AfterDurationWithBackoff(Duration::from_secs(u64::MAX)), Some(Ok(val)) if val == 0 => Fail::Immediately, Some(Ok(val)) => Fail::AfterDurationWithBackoff(Duration::from_millis( val.try_into().expect("i64 can be repsented by u64"), )), Some(Err(_)) if self.lenient_config => mk_default(), Some(Err(err)) => return Err(err), None => mk_default(), }; out[idx] = lock_mode; } Ok((out[0], out[1])) } /// The path to the user-level excludes file to ignore certain files in the worktree. pub(crate) fn excludes_file(&self) -> Option<Result<PathBuf, git_config::path::interpolate::Error>> { self.trusted_file_path("core", None, "excludesFile")? .map(|p| p.into_owned()) .into() } /// A helper to obtain a file from trusted configuration at `section_name`, `subsection_name`, and `key`, which is interpolated /// if present. pub(crate) fn trusted_file_path( &self, section_name: impl AsRef<str>, subsection_name: Option<&str>, key: impl AsRef<str>, ) -> Option<Result<Cow<'_, std::path::Path>, git_config::path::interpolate::Error>> { let path = self.resolved.path_filter( section_name, subsection_name, key, &mut self.filter_config_section.clone(), )?; let install_dir = crate::path::install_dir().ok(); let home = self.home_dir(); let ctx = crate::config::cache::interpolate_context(install_dir.as_deref(), home.as_deref()); Some(path.interpolate(ctx)) } pub(crate) fn apply_leniency<T, E>(&self, res: Option<Result<T, E>>) -> Result<Option<T>, E> { match res { Some(Ok(v)) => Ok(Some(v)), Some(Err(_err)) if self.lenient_config => Ok(None), Some(Err(err)) => Err(err), None => Ok(None), } } /// Collect everything needed to checkout files into a worktree. /// Note that some of the options being returned will be defaulted so safe settings, the caller might have to override them /// depending on the use-case. pub(crate) fn checkout_options( &self, git_dir: &std::path::Path, ) -> Result<git_worktree::index::checkout::Options, checkout_options::Error> { fn checkout_thread_limit_from_config( config: &git_config::File<'static>, ) -> Option<Result<usize, checkout_options::Error>> { config.integer("checkout", None, "workers").map(|val| match val { Ok(v) if v < 0 => Ok(0), Ok(v) => Ok(v.try_into().expect("positive i64 can always be usize on 64 bit")), Err(err) => Err(checkout_options::Error::Configuration { key: "checkout.workers", source: err, }), }) } fn boolean(me: &Cache, full_key: &'static str, default: bool) -> Result<bool, checkout_options::Error> { let mut tokens = full_key.split('.'); let section = tokens.next().expect("section"); let key = tokens.next().expect("key"); assert!(tokens.next().is_none(), "core.<key>"); Ok(me .apply_leniency(me.resolved.boolean(section, None, key)) .map_err(|err| checkout_options::Error::Configuration { key: full_key, source: err, })? .unwrap_or(default)) } fn assemble_attribute_globals( me: &Cache, _git_dir: &std::path::Path, ) -> Result<git_attributes::MatchGroup, checkout_options::Error> { let _attributes_file = match me.trusted_file_path("core", None, "attributesFile").transpose()? { Some(attributes) => Some(attributes.into_owned()), None => me.xdg_config_path("attributes").ok().flatten(), }; // TODO: implement git_attributes::MatchGroup::<git_attributes::Attributes>::from_git_dir(), similar to what's done for `Ignore`. Ok(Default::default()) } let thread_limit = self.apply_leniency(checkout_thread_limit_from_config(&self.resolved))?; Ok(git_worktree::index::checkout::Options { fs: git_worktree::fs::Capabilities { precompose_unicode: boolean(self, "core.precomposeUnicode", false)?, ignore_case: boolean(self, "core.ignoreCase", false)?, executable_bit: boolean(self, "core.fileMode", true)?, symlink: boolean(self, "core.symlinks", true)?, }, thread_limit, destination_is_initially_empty: false, overwrite_existing: false, keep_going: false, trust_ctime: boolean(self, "core.trustCTime", true)?, check_stat: self .resolved .string("core", None, "checkStat") .map_or(true, |v| v.as_ref() != "minimal"), attribute_globals: assemble_attribute_globals(self, git_dir)?, }) } pub(crate) fn xdg_config_path( &self, resource_file_name: &str, ) -> Result<Option<PathBuf>, git_sec::permission::Error<PathBuf>> { std::env::var_os("XDG_CONFIG_HOME") .map(|path| (PathBuf::from(path), &self.xdg_config_home_env)) .or_else(|| { std::env::var_os("HOME").map(|path| { ( { let mut p = PathBuf::from(path); p.push(".config"); p }, &self.home_env, ) }) }) .and_then(|(base, permission)| { let resource = base.join("git").join(resource_file_name); permission.check(resource).transpose() }) .transpose() } /// Return the home directory if we are allowed to read it and if it is set in the environment. /// /// We never fail for here even if the permission is set to deny as we `git-config` will fail later /// if it actually wants to use the home directory - we don't want to fail prematurely. pub(crate) fn home_dir(&self) -> Option<PathBuf> { std::env::var_os("HOME") .map(PathBuf::from) .and_then(|path| self.home_env.check_opt(path)) }}
use std::{borrow::Cow, convert::TryInto, path::PathBuf, time::Duration};
use git_lock::acquire::Fail;
use crate::{
config::{cache::util::check_lenient_default, checkout_options, Cache},
remote,
repository::identity,
};
/// Access
impl Cache {
pub(crate) fn diff_algorithm(&self) -> Result<git_diff::text::Algorithm, crate::config::diff::algorithm::Error> {
use crate::config::diff::algorithm::Error;
self.diff_algorithm
.get_or_try_init(|| {
let res = {
let name = self
.resolved
.string("diff", None, "algorithm")
.unwrap_or_else(|| Cow::Borrowed("myers".into()));
if name.eq_ignore_ascii_case(b"myers") || name.eq_ignore_ascii_case(b"default") {
Ok(git_diff::text::Algorithm::Myers)
} else if name.eq_ignore_ascii_case(b"minimal") {
Ok(git_diff::text::Algorithm::MyersMinimal)
} else if name.eq_ignore_ascii_case(b"histogram") {
Ok(git_diff::text::Algorithm::Histogram)
} else if name.eq_ignore_ascii_case(b"patience") {
if self.lenient_config {
Ok(git_diff::text::Algorithm::Histogram)
} else {
Err(Error::Unimplemented {
name: name.into_owned(),
})
}
} else {
Err(Error::Unknown {
name: name.into_owned(),
})
}
};
check_lenient_default(res, self.lenient_config, || git_diff::text::Algorithm::Myers)
})
.copied()
}
pub(crate) fn personas(&self) -> &identity::Personas {
self.personas
.get_or_init(|| identity::Personas::from_config_and_env(&self.resolved, self.git_prefix))
}
pub(crate) fn url_rewrite(&self) -> &remote::url::Rewrite {
self.url_rewrite
.get_or_init(|| remote::url::Rewrite::from_config(&self.resolved, self.filter_config_section))
}
#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
pub(crate) fn url_scheme(
&self,
) -> Result<&remote::url::SchemePermission, remote::url::scheme_permission::init::Error> {
self.url_scheme.get_or_try_init(|| {
remote::url::SchemePermission::from_config(&self.resolved, self.git_prefix, self.filter_config_section)
})
}
/// Returns (file-timeout, pack-refs timeout)
pub(crate) fn lock_timeout(
&self,
) -> Result<(git_lock::acquire::Fail, git_lock::acquire::Fail), git_config::value::Error> {
enum Kind {
RefFiles,
RefPackFile,
}
let mut out: [git_lock::acquire::Fail; 2] = Default::default();
for (idx, kind) in [Kind::RefFiles, Kind::RefPackFile].iter().enumerate() {
let (key, default_ms) = match kind {
Kind::RefFiles => ("filesRefLockTimeout", 100),
Kind::RefPackFile => ("packedRefsTimeout", 1000),
};
let mk_default = || Fail::AfterDurationWithBackoff(Duration::from_millis(default_ms));
let mut fnp = self.filter_config_section;
let lock_mode = match self.resolved.integer_filter("core", None, key, &mut fnp) {
Some(Ok(val)) if val < 0 => Fail::AfterDurationWithBackoff(Duration::from_secs(u64::MAX)),
Some(Ok(val)) if val == 0 => Fail::Immediately,
Some(Ok(val)) => Fail::AfterDurationWithBackoff(Duration::from_millis(
val.try_into().expect("i64 can be repsented by u64"),
)),
Some(Err(_)) if self.lenient_config => mk_default(),
Some(Err(err)) => return Err(err),
None => mk_default(),
};
out[idx] = lock_mode;
}
Ok((out[0], out[1]))
}
/// The path to the user-level excludes file to ignore certain files in the worktree.
pub(crate) fn excludes_file(&self) -> Option<Result<PathBuf, git_config::path::interpolate::Error>> {
self.trusted_file_path("core", None, "excludesFile")?
.map(|p| p.into_owned())
.into()
}
/// A helper to obtain a file from trusted configuration at `section_name`, `subsection_name`, and `key`, which is interpolated
/// if present.
pub(crate) fn trusted_file_path(
&self,
section_name: impl AsRef<str>,
subsection_name: Option<&str>,
key: impl AsRef<str>,
) -> Option<Result<Cow<'_, std::path::Path>, git_config::path::interpolate::Error>> {
let path = self.resolved.path_filter(
section_name,
subsection_name,
key,
&mut self.filter_config_section.clone(),
)?;
let install_dir = crate::path::install_dir().ok();
let home = self.home_dir();
let ctx = crate::config::cache::interpolate_context(install_dir.as_deref(), home.as_deref());
Some(path.interpolate(ctx))
}
pub(crate) fn apply_leniency<T, E>(&self, res: Option<Result<T, E>>) -> Result<Option<T>, E> {
match res {
Some(Ok(v)) => Ok(Some(v)),
Some(Err(_err)) if self.lenient_config => Ok(None),
Some(Err(err)) => Err(err),
None => Ok(None),
}
}
/// Collect everything needed to checkout files into a worktree.
/// Note that some of the options being returned will be defaulted so safe settings, the caller might have to override them
/// depending on the use-case.
pub(crate) fn checkout_options(
&self,
git_dir: &std::path::Path,
) -> Result<git_worktree::index::checkout::Options, checkout_options::Error> {
fn checkout_thread_limit_from_config(
config: &git_config::File<'static>,
) -> Option<Result<usize, checkout_options::Error>> {
config.integer("checkout", None, "workers").map(|val| match val {
Ok(v) if v < 0 => Ok(0),
Ok(v) => Ok(v.try_into().expect("positive i64 can always be usize on 64 bit")),
Err(err) => Err(checkout_options::Error::Configuration {
key: "checkout.workers",
source: err,
}),
})
}
fn boolean(me: &Cache, full_key: &'static str, default: bool) -> Result<bool, checkout_options::Error> {
let mut tokens = full_key.split('.');
let section = tokens.next().expect("section");
let key = tokens.next().expect("key");
assert!(tokens.next().is_none(), "core.<key>");
Ok(me
.apply_leniency(me.resolved.boolean(section, None, key))
.map_err(|err| checkout_options::Error::Configuration {
key: full_key,
source: err,
})?
.unwrap_or(default))
}
fn assemble_attribute_globals(
me: &Cache,
_git_dir: &std::path::Path,
) -> Result<git_attributes::MatchGroup, checkout_options::Error> {
let _attributes_file = match me.trusted_file_path("core", None, "attributesFile").transpose()? {
Some(attributes) => Some(attributes.into_owned()),
None => me.xdg_config_path("attributes").ok().flatten(),
};
// TODO: implement git_attributes::MatchGroup::<git_attributes::Attributes>::from_git_dir(), similar to what's done for `Ignore`.
Ok(Default::default())
}
let thread_limit = self.apply_leniency(checkout_thread_limit_from_config(&self.resolved))?;
Ok(git_worktree::index::checkout::Options {
fs: git_worktree::fs::Capabilities {
precompose_unicode: boolean(self, "core.precomposeUnicode", false)?,
ignore_case: boolean(self, "core.ignoreCase", false)?,
executable_bit: boolean(self, "core.fileMode", true)?,
symlink: boolean(self, "core.symlinks", true)?,
},
thread_limit,
destination_is_initially_empty: false,
overwrite_existing: false,
keep_going: false,
trust_ctime: boolean(self, "core.trustCTime", true)?,
check_stat: self
.resolved
.string("core", None, "checkStat")
.map_or(true, |v| v.as_ref() != "minimal"),
attribute_globals: assemble_attribute_globals(self, git_dir)?,
})
}
pub(crate) fn xdg_config_path(
&self,
resource_file_name: &str,
) -> Result<Option<PathBuf>, git_sec::permission::Error<PathBuf>> {
std::env::var_os("XDG_CONFIG_HOME")
.map(|path| (PathBuf::from(path), &self.xdg_config_home_env))
.or_else(|| {
std::env::var_os("HOME").map(|path| {
(
{
let mut p = PathBuf::from(path);
p.push(".config");
p
},
&self.home_env,
)
})
})
.and_then(|(base, permission)| {
let resource = base.join("git").join(resource_file_name);
permission.check(resource).transpose()
})
.transpose()
}
/// Return the home directory if we are allowed to read it and if it is set in the environment.
///
/// We never fail for here even if the permission is set to deny as we `git-config` will fail later
/// if it actually wants to use the home directory - we don't want to fail prematurely.
pub(crate) fn home_dir(&self) -> Option<PathBuf> {
std::env::var_os("HOME")
.map(PathBuf::from)
.and_then(|path| self.home_env.check_opt(path))
}
}