Skip to main content

gix_config/
source.rs

1use std::{ffi::OsString, path::PathBuf};
2
3use crate::Source;
4
5/// The category of a [`Source`], in order of ascending precedence.
6#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
7pub enum Kind {
8    /// A special configuration file that ships with the git installation, and is thus tied to the used git binary.
9    GitInstallation,
10    /// A source shared for the entire system.
11    System,
12    /// Application specific configuration unique for each user of the `System`.
13    Global,
14    /// Configuration relevant only to the repository, possibly including the worktree.
15    Repository,
16    /// Configuration specified after all other configuration was loaded for the purpose of overrides.
17    Override,
18}
19
20impl Kind {
21    /// Return a list of sources associated with this `Kind` of source, in order of ascending precedence.
22    pub fn sources(self) -> &'static [Source] {
23        let src = match self {
24            Kind::GitInstallation => &[Source::GitInstallation] as &[_],
25            Kind::System => &[Source::System],
26            Kind::Global => &[Source::Git, Source::User],
27            Kind::Repository => &[Source::Local, Source::Worktree],
28            Kind::Override => &[Source::Env, Source::Cli, Source::Api],
29        };
30        debug_assert!(
31            src.iter().all(|src| src.kind() == self),
32            "BUG: classification of source has to match the ordering here, see `Source::kind()`"
33        );
34        src
35    }
36}
37
38impl Source {
39    /// Return true if the source indicates a location within a file of a repository.
40    pub const fn kind(self) -> Kind {
41        use Source::*;
42        match self {
43            GitInstallation => Kind::GitInstallation,
44            System => Kind::System,
45            Git | User => Kind::Global,
46            Local | Worktree => Kind::Repository,
47            Env | Cli | Api | EnvOverride => Kind::Override,
48        }
49    }
50
51    /// Returns the location at which a file of this type would be stored, or `None` if
52    /// there is no notion of persistent storage for this source, with `env_var` to obtain environment variables.
53    /// Note that the location can be relative for repository-local sources like `Local` and `Worktree`,
54    /// and the caller has to known which base it is relative to, namely the `common_dir` in the `Local` case
55    /// and the `git_dir` in the `Worktree` case.
56    /// Be aware that depending on environment overrides, multiple scopes might return the same path, which should
57    /// only be loaded once nonetheless.
58    ///
59    /// With `env_var` it becomes possible to prevent accessing environment variables entirely to comply with `gix-sec`
60    /// permissions for example.
61    pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option<OsString>) -> Option<PathBuf> {
62        use Source::*;
63        match self {
64            GitInstallation | System => {
65                if env_var("GIT_CONFIG_NOSYSTEM")
66                    .map(crate::Boolean::try_from)
67                    .transpose()
68                    .ok()
69                    .flatten()
70                    .is_some_and(|b| b.0)
71                {
72                    None
73                } else {
74                    let is_system_scoped = match self {
75                        GitInstallation => gix_path::env::installation_config_is_system(),
76                        System => true,
77                        _ => unreachable!("matched installation or system source"),
78                    };
79                    let system_override = is_system_scoped.then(|| env_var("GIT_CONFIG_SYSTEM")).flatten();
80                    if let Some(path) = system_override {
81                        return Some(path.into());
82                    }
83                    match self {
84                        GitInstallation => gix_path::env::installation_config().map(Into::into),
85                        System => gix_path::env::system_config().map(Into::into),
86                        _ => unreachable!("matched installation or system source"),
87                    }
88                }
89            }
90            Git => match env_var("GIT_CONFIG_GLOBAL") {
91                Some(global_override) => Some(PathBuf::from(global_override)),
92                None => gix_path::env::xdg_config("config", env_var),
93            },
94            User => env_var("GIT_CONFIG_GLOBAL").map(PathBuf::from).or_else(|| {
95                env_var("HOME")
96                    .map(PathBuf::from)
97                    .or_else(|| {
98                        if cfg!(windows) {
99                            // On Windows, HOME is rarely set, and we generally need something more.
100                            std::env::home_dir()
101                        } else {
102                            // Git also only tries the env var on unix, and so do we
103                            None
104                        }
105                    })
106                    .map(|mut p| {
107                        p.push(".gitconfig");
108                        p
109                    })
110            }),
111            Local => Some("config".into()),
112            Worktree => Some("config.worktree".into()),
113            Env | Cli | Api | EnvOverride => None,
114        }
115    }
116}