diff options
Diffstat (limited to 'vendor/gix-config/src/file/init')
-rw-r--r-- | vendor/gix-config/src/file/init/comfort.rs | 159 | ||||
-rw-r--r-- | vendor/gix-config/src/file/init/from_env.rs | 88 | ||||
-rw-r--r-- | vendor/gix-config/src/file/init/from_paths.rs | 94 | ||||
-rw-r--r-- | vendor/gix-config/src/file/init/mod.rs | 86 | ||||
-rw-r--r-- | vendor/gix-config/src/file/init/types.rs | 47 |
5 files changed, 474 insertions, 0 deletions
diff --git a/vendor/gix-config/src/file/init/comfort.rs b/vendor/gix-config/src/file/init/comfort.rs new file mode 100644 index 000000000..ffe859a1a --- /dev/null +++ b/vendor/gix-config/src/file/init/comfort.rs @@ -0,0 +1,159 @@ +use std::path::PathBuf; + +use crate::{ + file::{init, Metadata}, + path, source, File, Source, +}; + +/// Easy-instantiation of typical non-repository git configuration files with all configuration defaulting to typical values. +/// +/// ### Limitations +/// +/// Note that `includeIf` conditions in global files will cause failure as the required information +/// to resolve them isn't present without a repository. +/// +/// Also note that relevant information to interpolate paths will be obtained from the environment or other +/// source on unix. +impl File<'static> { + /// Open all global configuration files which involves the following sources: + /// + /// * [system][crate::Source::System] + /// * [git][crate::Source::Git] + /// * [user][crate::Source::User] + /// + /// which excludes repository local configuration, as well as override-configuration from environment variables. + /// + /// Note that the file might [be empty][File::is_void()] in case no configuration file was found. + pub fn from_globals() -> Result<File<'static>, init::from_paths::Error> { + let metas = [source::Kind::System, source::Kind::Global] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + let path = source + .storage_location(&mut |name| std::env::var_os(name)) + .and_then(|p| p.is_file().then_some(p)) + .map(|p| p.into_owned()); + + Metadata { + path, + source: *source, + level: 0, + trust: gix_sec::Trust::Full, + } + .into() + }); + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow_without_conditional(home.as_deref()), + ..Default::default() + }; + File::from_paths_metadata(metas, options).map(Option::unwrap_or_default) + } + + /// Generates a config from `GIT_CONFIG_*` environment variables and return a possibly empty `File`. + /// A typical use of this is to [`append`][File::append()] this configuration to another one with lower + /// precedence to obtain overrides. + /// + /// See [`gix-config`'s documentation] for more information on the environment variables in question. + /// + /// [`gix-config`'s documentation]: https://git-scm.com/docs/gix-config#Documentation/gix-config.txt-GITCONFIGCOUNT + pub fn from_environment_overrides() -> Result<File<'static>, init::from_env::Error> { + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow_without_conditional(home.as_deref()), + ..Default::default() + }; + + File::from_env(options).map(Option::unwrap_or_default) + } +} + +/// An easy way to provide complete configuration for a repository. +impl File<'static> { + /// This configuration type includes the following sources, in order of precedence: + /// + /// - globals + /// - repository-local by loading `dir`/config + /// - worktree by loading `dir`/config.worktree + /// - environment + /// + /// Note that `dir` is the `.git` dir to load the configuration from, not the configuration file. + /// + /// Includes will be resolved within limits as some information like the git installation directory is missing to interpolate + /// paths with as well as git repository information like the branch name. + pub fn from_git_dir(dir: impl Into<std::path::PathBuf>) -> Result<File<'static>, from_git_dir::Error> { + let (mut local, git_dir) = { + let source = Source::Local; + let mut path = dir.into(); + path.push( + source + .storage_location(&mut |n| std::env::var_os(n)) + .expect("location available for local"), + ); + let local = Self::from_path_no_includes(&path, source)?; + path.pop(); + (local, path) + }; + + let worktree = match local.boolean("extensions", None, "worktreeConfig") { + Some(Ok(worktree_config)) => worktree_config.then(|| { + let source = Source::Worktree; + let path = git_dir.join( + source + .storage_location(&mut |n| std::env::var_os(n)) + .expect("location available for worktree"), + ); + Self::from_path_no_includes(path, source) + }), + _ => None, + } + .transpose()?; + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow( + path::interpolate::Context { + home_dir: home.as_deref(), + ..Default::default() + }, + init::includes::conditional::Context { + git_dir: Some(git_dir.as_ref()), + branch_name: None, + }, + ), + lossy: false, + }; + + let mut globals = Self::from_globals()?; + globals.resolve_includes(options)?; + local.resolve_includes(options)?; + + globals.append(local); + if let Some(mut worktree) = worktree { + worktree.resolve_includes(options)?; + globals.append(worktree); + } + globals.append(Self::from_environment_overrides()?); + + Ok(globals) + } +} + +/// +pub mod from_git_dir { + use crate::file::init; + + /// The error returned by [`File::from_git_dir()`][crate::File::from_git_dir()]. + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + FromPaths(#[from] init::from_paths::Error), + #[error(transparent)] + FromEnv(#[from] init::from_env::Error), + #[error(transparent)] + Init(#[from] init::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), + } +} diff --git a/vendor/gix-config/src/file/init/from_env.rs b/vendor/gix-config/src/file/init/from_env.rs new file mode 100644 index 000000000..167d37399 --- /dev/null +++ b/vendor/gix-config/src/file/init/from_env.rs @@ -0,0 +1,88 @@ +use std::convert::TryFrom; + +use bstr::{BStr, ByteSlice}; + +use crate::{file, file::init, parse, parse::section, path::interpolate, File}; + +/// Represents the errors that may occur when calling [`File::from_env()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("Configuration {kind} at index {index} contained illformed UTF-8")] + IllformedUtf8 { index: usize, kind: &'static str }, + #[error("GIT_CONFIG_COUNT was not a positive integer: {}", .input)] + InvalidConfigCount { input: String }, + #[error("GIT_CONFIG_KEY_{} was not set", .key_id)] + InvalidKeyId { key_id: usize }, + #[error("GIT_CONFIG_KEY_{} was set to an invalid value: {}", .key_id, .key_val)] + InvalidKeyValue { key_id: usize, key_val: String }, + #[error("GIT_CONFIG_VALUE_{} was not set", .value_id)] + InvalidValueId { value_id: usize }, + #[error(transparent)] + PathInterpolationError(#[from] interpolate::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), + #[error(transparent)] + Section(#[from] section::header::Error), + #[error(transparent)] + Key(#[from] section::key::Error), +} + +/// Instantiation from environment variables +impl File<'static> { + /// Generates a config from `GIT_CONFIG_*` environment variables or returns `Ok(None)` if no configuration was found. + /// See [`gix-config`'s documentation] for more information on the environment variables in question. + /// + /// With `options` configured, it's possible to resolve `include.path` or `includeIf.<condition>.path` directives as well. + /// + /// [`gix-config`'s documentation]: https://git-scm.com/docs/gix-config#Documentation/gix-config.txt-GITCONFIGCOUNT + pub fn from_env(options: init::Options<'_>) -> Result<Option<File<'static>>, Error> { + use std::env; + let count: usize = match env::var("GIT_CONFIG_COUNT") { + Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, + Err(_) => return Ok(None), + }; + + if count == 0 { + return Ok(None); + } + + let meta = file::Metadata { + path: None, + source: crate::Source::Env, + level: 0, + trust: gix_sec::Trust::Full, + }; + let mut config = File::new(meta); + for i in 0..count { + let key = gix_path::os_string_into_bstring( + env::var_os(format!("GIT_CONFIG_KEY_{i}")).ok_or(Error::InvalidKeyId { key_id: i })?, + ) + .map_err(|_| Error::IllformedUtf8 { index: i, kind: "key" })?; + let value = env::var_os(format!("GIT_CONFIG_VALUE_{i}")).ok_or(Error::InvalidValueId { value_id: i })?; + let key = parse::key(<_ as AsRef<BStr>>::as_ref(&key)).ok_or_else(|| Error::InvalidKeyValue { + key_id: i, + key_val: key.to_string(), + })?; + + config + .section_mut_or_create_new(key.section_name, key.subsection_name)? + .push( + section::Key::try_from(key.value_name.to_owned())?, + Some( + gix_path::os_str_into_bstr(&value) + .map_err(|_| Error::IllformedUtf8 { + index: i, + kind: "value", + })? + .as_bytes() + .into(), + ), + ); + } + + let mut buf = Vec::new(); + init::includes::resolve(&mut config, &mut buf, options)?; + Ok(Some(config)) + } +} diff --git a/vendor/gix-config/src/file/init/from_paths.rs b/vendor/gix-config/src/file/init/from_paths.rs new file mode 100644 index 000000000..5d671b69e --- /dev/null +++ b/vendor/gix-config/src/file/init/from_paths.rs @@ -0,0 +1,94 @@ +use std::collections::BTreeSet; + +use crate::{ + file::{init, init::Options, Metadata}, + File, +}; + +/// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_no_includes()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Init(#[from] init::Error), +} + +/// Instantiation from one or more paths +impl File<'static> { + /// Load the single file at `path` with `source` without following include directives. + /// + /// Note that the path will be checked for ownership to derive trust. + pub fn from_path_no_includes(path: impl Into<std::path::PathBuf>, source: crate::Source) -> Result<Self, Error> { + let path = path.into(); + let trust = gix_sec::Trust::from_path_ownership(&path)?; + + let mut buf = Vec::new(); + std::io::copy(&mut std::fs::File::open(&path)?, &mut buf)?; + + Ok(File::from_bytes_owned( + &mut buf, + Metadata::from(source).at(path).with(trust), + Default::default(), + )?) + } + + /// Constructs a `gix-config` file from the provided metadata, which must include a path to read from or be ignored. + /// Returns `Ok(None)` if there was not a single input path provided, which is a possibility due to + /// [`Metadata::path`] being an `Option`. + /// If an input path doesn't exist, the entire operation will abort. See [`from_paths_metadata_buf()`][Self::from_paths_metadata_buf()] + /// for a more powerful version of this method. + pub fn from_paths_metadata( + path_meta: impl IntoIterator<Item = impl Into<Metadata>>, + options: Options<'_>, + ) -> Result<Option<Self>, Error> { + let mut buf = Vec::with_capacity(512); + let err_on_nonexisting_paths = true; + Self::from_paths_metadata_buf(path_meta, &mut buf, err_on_nonexisting_paths, options) + } + + /// Like [from_paths_metadata()][Self::from_paths_metadata()], but will use `buf` to temporarily store the config file + /// contents for parsing instead of allocating an own buffer. + /// + /// If `err_on_nonexisting_paths` is false, instead of aborting with error, we will continue to the next path instead. + pub fn from_paths_metadata_buf( + path_meta: impl IntoIterator<Item = impl Into<Metadata>>, + buf: &mut Vec<u8>, + err_on_non_existing_paths: bool, + options: Options<'_>, + ) -> Result<Option<Self>, Error> { + let mut target = None; + let mut seen = BTreeSet::default(); + for (path, mut meta) in path_meta.into_iter().filter_map(|meta| { + let mut meta = meta.into(); + meta.path.take().map(|p| (p, meta)) + }) { + if !seen.insert(path.clone()) { + continue; + } + + buf.clear(); + std::io::copy( + &mut match std::fs::File::open(&path) { + Ok(f) => f, + Err(err) if !err_on_non_existing_paths && err.kind() == std::io::ErrorKind::NotFound => continue, + Err(err) => return Err(err.into()), + }, + buf, + )?; + meta.path = Some(path); + + let config = Self::from_bytes_owned(buf, meta, options)?; + match &mut target { + None => { + target = Some(config); + } + Some(target) => { + target.append(config); + } + } + } + Ok(target) + } +} diff --git a/vendor/gix-config/src/file/init/mod.rs b/vendor/gix-config/src/file/init/mod.rs new file mode 100644 index 000000000..5b4030241 --- /dev/null +++ b/vendor/gix-config/src/file/init/mod.rs @@ -0,0 +1,86 @@ +use gix_features::threading::OwnShared; + +use crate::{ + file::{includes, section, Metadata}, + parse, File, +}; + +mod types; +pub use types::{Error, Options}; + +mod comfort; +/// +pub mod from_env; +/// +pub mod from_paths; + +impl<'a> File<'a> { + /// Return an empty `File` with the given `meta`-data to be attached to all new sections. + pub fn new(meta: impl Into<OwnShared<Metadata>>) -> Self { + Self { + frontmatter_events: Default::default(), + frontmatter_post_section: Default::default(), + section_lookup_tree: Default::default(), + sections: Default::default(), + section_id_counter: 0, + section_order: Default::default(), + meta: meta.into(), + } + } + + /// Instantiate a new `File` from given `input`, associating each section and their values with + /// `meta`-data, while respecting `options`. + pub fn from_bytes_no_includes( + input: &'a [u8], + meta: impl Into<OwnShared<Metadata>>, + options: Options<'_>, + ) -> Result<Self, Error> { + let meta = meta.into(); + Ok(Self::from_parse_events_no_includes( + parse::Events::from_bytes(input, options.to_event_filter())?, + meta, + )) + } + + /// Instantiate a new `File` from given `events`, associating each section and their values with + /// `meta`-data. + pub fn from_parse_events_no_includes( + parse::Events { frontmatter, sections }: parse::Events<'a>, + meta: impl Into<OwnShared<Metadata>>, + ) -> Self { + let meta = meta.into(); + let mut this = File::new(OwnShared::clone(&meta)); + + this.frontmatter_events = frontmatter; + + for section in sections { + this.push_section_internal(crate::file::Section { + header: section.header, + body: section::Body(section.events), + meta: OwnShared::clone(&meta), + id: Default::default(), + }); + } + + this + } +} + +impl File<'static> { + /// Instantiate a new fully-owned `File` from given `input` (later reused as buffer when resolving includes), + /// associating each section and their values with `meta`-data, while respecting `options`, and + /// following includes as configured there. + pub fn from_bytes_owned( + input_and_buf: &mut Vec<u8>, + meta: impl Into<OwnShared<Metadata>>, + options: Options<'_>, + ) -> Result<Self, Error> { + let mut config = Self::from_parse_events_no_includes( + parse::Events::from_bytes_owned(input_and_buf, options.to_event_filter()).map_err(Error::from)?, + meta, + ); + + includes::resolve(&mut config, input_and_buf, options).map_err(Error::from)?; + Ok(config) + } +} diff --git a/vendor/gix-config/src/file/init/types.rs b/vendor/gix-config/src/file/init/types.rs new file mode 100644 index 000000000..fcb17c0ca --- /dev/null +++ b/vendor/gix-config/src/file/init/types.rs @@ -0,0 +1,47 @@ +use crate::{file::init, parse, parse::Event, path::interpolate}; + +/// The error returned by [`File::from_bytes_no_includes()`][crate::File::from_bytes_no_includes()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), +} + +/// Options when loading git config using [`File::from_paths_metadata()`][crate::File::from_paths_metadata()]. +#[derive(Clone, Copy, Default)] +pub struct Options<'a> { + /// Configure how to follow includes while handling paths. + pub includes: init::includes::Options<'a>, + /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. + /// + /// Note that doing so will degenerate [`write_to()`][crate::File::write_to()] and strip it off its comments + /// and additional whitespace entirely, but will otherwise be a valid configuration file. + pub lossy: bool, +} + +impl Options<'_> { + pub(crate) fn to_event_filter(self) -> Option<fn(&Event<'_>) -> bool> { + if self.lossy { + Some(discard_nonessential_events) + } else { + None + } + } +} + +fn discard_nonessential_events(e: &Event<'_>) -> bool { + match e { + Event::Whitespace(_) | Event::Comment(_) | Event::Newline(_) => false, + Event::SectionHeader(_) + | Event::SectionKey(_) + | Event::KeyValueSeparator + | Event::Value(_) + | Event::ValueNotDone(_) + | Event::ValueDone(_) => true, + } +} |