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
use crate::{parse, path::interpolate};

/// The error returned when following includes.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
    #[error(transparent)]
    Io(#[from] 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()
    }
}

///
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>,
    }
}