Skip to main content

gix_config/file/init/
comfort.rs

1use crate::{
2    File, Source,
3    file::{Metadata, init},
4    path, source,
5};
6use gix_error::{ExnMessageResult, ExnResult};
7
8/// Easy-instantiation of typical non-repository git configuration files with all configuration defaulting to typical values.
9///
10/// ### Limitations
11///
12/// Note that `includeIf` conditions in global files will cause failure as the required information
13/// to resolve them isn't present without a repository.
14///
15/// Also note that relevant information to interpolate paths will be obtained from the environment or other
16/// source on unix.
17impl File {
18    /// Open all global configuration files which involves the following sources:
19    ///
20    /// * [git-installation](source::Kind::GitInstallation)
21    /// * [system](source::Kind::System)
22    /// * [globals](source::Kind::Global)
23    ///
24    /// which excludes repository local configuration, as well as override-configuration from environment variables.
25    ///
26    /// Note that the file might [be empty][File::is_void()] in case no configuration file was found.
27    pub fn from_globals() -> ExnMessageResult<File> {
28        let metas = [
29            source::Kind::GitInstallation,
30            source::Kind::System,
31            source::Kind::Global,
32        ]
33        .iter()
34        .flat_map(|kind| kind.sources())
35        .filter_map(|source| {
36            let path = source
37                .storage_location(&mut gix_path::env::var)
38                .and_then(|p| p.is_file().then_some(p));
39
40            Metadata {
41                path,
42                source: *source,
43                level: 0,
44                trust: gix_sec::Trust::Full,
45            }
46            .into()
47        });
48
49        let home = gix_path::env::home_dir();
50        let options = init::Options {
51            includes: init::includes::Options::follow_without_conditional(home.as_deref()),
52            ..Default::default()
53        };
54        File::from_paths_metadata(metas, options).map(Option::unwrap_or_default)
55    }
56
57    /// Generates a config from `GIT_CONFIG_*` environment variables and return a possibly empty `File`.
58    /// A typical use of this is to [`append`][File::append()] this configuration to another one with lower
59    /// precedence to obtain overrides.
60    ///
61    /// See [`git-config`'s documentation] for more information on the environment variables in question.
62    ///
63    /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT
64    pub fn from_environment_overrides() -> ExnResult<File> {
65        let home = gix_path::env::home_dir();
66        let options = init::Options {
67            includes: init::includes::Options::follow_without_conditional(home.as_deref()),
68            ..Default::default()
69        };
70
71        File::from_env(options).map(Option::unwrap_or_default)
72    }
73}
74
75/// An easy way to provide complete configuration for a repository.
76impl File {
77    /// This configuration type includes the following sources, in order of precedence:
78    ///
79    /// - globals
80    /// - repository-local by loading `dir`/config
81    /// - worktree by loading `dir`/config.worktree
82    /// - environment
83    ///
84    /// Note that `dir` is the `.git` dir to load the configuration from, not the configuration file.
85    ///
86    /// Includes will be resolved within limits as some information like the git installation directory is missing to interpolate
87    /// paths with as well as git repository information like the branch name.
88    pub fn from_git_dir(dir: std::path::PathBuf) -> ExnMessageResult<File> {
89        use gix_error::{ResultExt, message};
90
91        let (mut local, git_dir) = {
92            let source = Source::Local;
93            let mut path = dir;
94            path.push(
95                source
96                    .storage_location(&mut gix_path::env::var)
97                    .expect("location available for local"),
98            );
99            let local = Self::from_path_no_includes(path.clone(), source)
100                .or_raise(|| message("Could not read repository-local configuration"))?;
101            path.pop();
102            (local, path)
103        };
104
105        let worktree = match local.boolean("extensions.worktreeConfig") {
106            Ok(Some(worktree_config)) => worktree_config.then(|| {
107                let source = Source::Worktree;
108                let path = git_dir.join(
109                    source
110                        .storage_location(&mut gix_path::env::var)
111                        .expect("location available for worktree"),
112                );
113                Self::from_path_no_includes(path, source)
114            }),
115            _ => None,
116        }
117        .transpose()
118        .or_raise(|| message("Could not read worktree configuration"))?;
119
120        let home = gix_path::env::home_dir();
121        let options = init::Options {
122            includes: init::includes::Options::follow(
123                path::interpolate::Context {
124                    home_dir: home.as_deref(),
125                    ..Default::default()
126                },
127                init::includes::conditional::Context {
128                    git_dir: Some(git_dir.as_ref()),
129                    branch_name: None,
130                },
131            ),
132            ..Default::default()
133        };
134
135        let mut globals = Self::from_globals().or_raise(|| message("Could not read global configuration"))?;
136        globals
137            .resolve_includes(options)
138            .or_raise(|| message("Could not resolve includes in global configuration"))?;
139        local
140            .resolve_includes(options)
141            .or_raise(|| message("Could not resolve includes in repository-local configuration"))?;
142
143        globals
144            .append(local)
145            .or_raise(|| message("Could not append repository-local configuration"))?;
146        if let Some(mut worktree) = worktree {
147            worktree
148                .resolve_includes(options)
149                .or_raise(|| message("Could not resolve includes in worktree configuration"))?;
150            globals
151                .append(worktree)
152                .or_raise(|| message("Could not append worktree configuration"))?;
153        }
154        let environment =
155            Self::from_environment_overrides().or_raise(|| message("Could not read environment configuration"))?;
156        globals
157            .append(environment)
158            .or_raise(|| message("Could not append environment configuration"))?;
159
160        Ok(globals)
161    }
162}