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}