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