gix_config/
source.rs

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