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