1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
use crate::{parse, path::interpolate};
use std::path::PathBuf;
/// The error returned when following includes.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Failed to copy configuration file into buffer")]
CopyBuffer(#[source] std::io::Error),
#[error("Could not read included configuration file at '{}'", path.display())]
Io { path: PathBuf, source: std::io::Error },
#[error(transparent)]
Parse(#[from] parse::Error),
#[error(transparent)]
Interpolate(#[from] interpolate::Error),
#[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)]
IncludeDepthExceeded { max_depth: u8 },
#[error("Include paths from environment variables must not be relative as no config file paths exists as root")]
MissingConfigPath,
#[error("The git directory must be provided to support `gitdir:` conditional includes")]
MissingGitDir,
#[error(transparent)]
Realpath(#[from] gix_path::realpath::Error),
}
/// Options to handle includes, like `include.path` or `includeIf.<condition>.path`,
#[derive(Clone, Copy)]
pub struct Options<'a> {
/// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0.
pub max_depth: u8,
/// When max depth is exceeded while following nested includes,
/// return an error if true or silently stop following resolve_includes.
///
/// Setting this value to false allows to read configuration with cycles,
/// which otherwise always results in an error.
pub err_on_max_depth_exceeded: bool,
/// If true, default false, failing to interpolate paths will result in an error.
///
/// Interpolation also happens if paths in conditional includes can't be interpolated.
pub err_on_interpolation_failure: bool,
/// If true, default true, configuration not originating from a path will cause errors when trying to resolve
/// relative include paths (which would require the including configuration's path).
pub err_on_missing_config_path: bool,
/// Used during path interpolation, both for include paths before trying to read the file, and for
/// paths used in conditional `gitdir` includes.
pub interpolate: interpolate::Context<'a>,
/// Additional context for conditional includes to work.
pub conditional: conditional::Context<'a>,
}
impl<'a> Options<'a> {
/// Provide options to never follow include directives at all.
pub fn no_follow() -> Self {
Options {
max_depth: 0,
err_on_max_depth_exceeded: false,
err_on_interpolation_failure: false,
err_on_missing_config_path: false,
interpolate: Default::default(),
conditional: Default::default(),
}
}
/// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts
/// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution.
/// Note that the follow-mode is `git`-style, following at most 10 indirections while
/// producing an error if the depth is exceeded.
pub fn follow(interpolate: interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self {
Options {
max_depth: 10,
err_on_max_depth_exceeded: true,
err_on_interpolation_failure: false,
err_on_missing_config_path: true,
interpolate,
conditional,
}
}
/// For use with `follow` type options, cause failure if an include path couldn't be interpolated or the depth limit is exceeded.
pub fn strict(mut self) -> Self {
self.err_on_interpolation_failure = true;
self.err_on_max_depth_exceeded = true;
self.err_on_missing_config_path = true;
self
}
/// Like [`follow`][Options::follow()], but without information to resolve `includeIf` directories as well as default
/// configuration to allow resolving `~username/` path. `home_dir` is required to resolve `~/` paths if set.
/// Note that `%(prefix)` paths cannot be interpolated with this configuration, use [`follow()`][Options::follow()]
/// instead for complete control.
pub fn follow_without_conditional(home_dir: Option<&'a std::path::Path>) -> Self {
Options {
max_depth: 10,
err_on_max_depth_exceeded: true,
err_on_interpolation_failure: false,
err_on_missing_config_path: true,
interpolate: interpolate::Context {
git_install_dir: None,
home_dir,
home_for_user: Some(interpolate::home_for_user),
},
conditional: Default::default(),
}
}
/// Set the context used for interpolation when interpolating paths to include as well as the paths
/// in `gitdir` conditional includes.
pub fn interpolate_with(mut self, context: interpolate::Context<'a>) -> Self {
self.interpolate = context;
self
}
}
impl Default for Options<'_> {
fn default() -> Self {
Self::no_follow()
}
}
///
#[allow(clippy::empty_docs)]
pub mod conditional {
/// Options to handle conditional includes like `includeIf.<condition>.path`.
#[derive(Clone, Copy, Default)]
pub struct Context<'a> {
/// The location of the .git directory. If `None`, `gitdir` conditions cause an error.
///
/// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`.
pub git_dir: Option<&'a std::path::Path>,
/// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error.
///
/// Used for conditional includes, e.g. `includeIf.onbranch:main.…`
pub branch_name: Option<&'a gix_ref::FullNameRef>,
}
}