Skip to main content

gix/config/cache/
init.rs

1#![allow(clippy::result_large_err)]
2use std::{borrow::Cow, ffi::OsString};
3
4use gix_sec::Permission;
5
6use super::{interpolate_context, util, Error, StageOne};
7use crate::{
8    bstr::BString,
9    config,
10    config::{
11        cache::util::ApplyLeniency,
12        tree::{gitoxide, Core, Gitoxide, Http},
13        Cache,
14    },
15    open,
16    repository::init::setup_objects,
17};
18
19/// Initialization
20impl Cache {
21    #[allow(clippy::too_many_arguments)]
22    pub fn from_stage_one(
23        StageOne {
24            git_dir_config,
25            mut buf,
26            lossy,
27            is_bare,
28            object_hash,
29            reflog: _,
30            precompose_unicode: _,
31            protect_windows: _,
32        }: StageOne,
33        git_dir: &std::path::Path,
34        branch_name: Option<&gix_ref::FullNameRef>,
35        filter_config_section: fn(&gix_config::file::Metadata) -> bool,
36        git_install_dir: Option<&std::path::Path>,
37        home: Option<&std::path::Path>,
38        environment @ open::permissions::Environment {
39            git_prefix,
40            ssh_prefix: _,
41            xdg_config_home: _,
42            home: _,
43            http_transport,
44            identity,
45            objects,
46        }: open::permissions::Environment,
47        attributes: open::permissions::Attributes,
48        open::permissions::Config {
49            git_binary: use_installation,
50            system: use_system,
51            git: use_git,
52            user: use_user,
53            env: use_env,
54            includes: use_includes,
55        }: open::permissions::Config,
56        lenient_config: bool,
57        api_config_overrides: &[BString],
58        cli_config_overrides: &[BString],
59    ) -> Result<Self, Error> {
60        let options = gix_config::file::init::Options {
61            includes: if use_includes {
62                gix_config::file::includes::Options::follow(
63                    interpolate_context(git_install_dir, home),
64                    gix_config::file::includes::conditional::Context {
65                        git_dir: git_dir.into(),
66                        branch_name,
67                    },
68                )
69            } else {
70                gix_config::file::includes::Options::no_follow()
71            },
72            ..util::base_options(lossy, lenient_config)
73        };
74
75        let config = {
76            let git_prefix = &git_prefix;
77            let mut metas = [
78                gix_config::source::Kind::GitInstallation,
79                gix_config::source::Kind::System,
80                gix_config::source::Kind::Global,
81            ]
82            .iter()
83            .flat_map(|kind| kind.sources())
84            .filter_map(|source| {
85                match source {
86                    gix_config::Source::GitInstallation if !use_installation => return None,
87                    gix_config::Source::System if !use_system => return None,
88                    gix_config::Source::Git if !use_git => return None,
89                    gix_config::Source::User if !use_user => return None,
90                    _ => {}
91                }
92                source
93                    .storage_location(&mut Self::make_source_env(environment))
94                    .map(|p| (source, p.into_owned()))
95            })
96            .map(|(source, path)| gix_config::file::Metadata {
97                path: Some(path),
98                source: *source,
99                level: 0,
100                trust: gix_sec::Trust::Full,
101            });
102
103            let err_on_nonexisting_paths = false;
104            let mut globals = gix_config::File::from_paths_metadata_buf(
105                &mut metas,
106                &mut buf,
107                err_on_nonexisting_paths,
108                gix_config::file::init::Options {
109                    includes: gix_config::file::includes::Options::no_follow(),
110                    ..options
111                },
112            )
113            .map_err(|err| match err {
114                gix_config::file::init::from_paths::Error::Init(err) => Error::from(err),
115                gix_config::file::init::from_paths::Error::Io { source, path } => Error::Io { source, path },
116            })?
117            .unwrap_or_default();
118
119            let local_meta = git_dir_config.meta_owned();
120            globals.append(git_dir_config);
121            globals.resolve_includes(options)?;
122            if use_env {
123                globals.append(gix_config::File::from_env(options)?.unwrap_or_default());
124            }
125            if !cli_config_overrides.is_empty() {
126                config::overrides::append(&mut globals, cli_config_overrides, gix_config::Source::Cli, |_| None)
127                    .map_err(|err| Error::ConfigOverrides {
128                        err,
129                        source: gix_config::Source::Cli,
130                    })?;
131            }
132            if !api_config_overrides.is_empty() {
133                config::overrides::append(&mut globals, api_config_overrides, gix_config::Source::Api, |_| None)
134                    .map_err(|err| Error::ConfigOverrides {
135                        err,
136                        source: gix_config::Source::Api,
137                    })?;
138            }
139            apply_environment_overrides(&mut globals, *git_prefix, http_transport, identity, objects)?;
140            globals.set_meta(local_meta);
141            globals
142        };
143
144        let hex_len = util::parse_core_abbrev(&config, object_hash).with_leniency(lenient_config)?;
145
146        use util::config_bool;
147        let reflog = util::query_refupdates(&config, lenient_config)?;
148        let refs_namespace = util::query_refs_namespace(&config, lenient_config)?;
149        let ignore_case = config_bool(&config, &Core::IGNORE_CASE, "core.ignoreCase", false, lenient_config)?;
150        let use_multi_pack_index = config_bool(
151            &config,
152            &Core::MULTIPACK_INDEX,
153            "core.multiPackIndex",
154            true,
155            lenient_config,
156        )?;
157        #[cfg(feature = "revision")]
158        let object_kind_hint = util::disambiguate_hint(&config, lenient_config)?;
159        let (static_pack_cache_limit_bytes, pack_cache_bytes, object_cache_bytes) =
160            util::parse_object_caches(&config, lenient_config, filter_config_section)?;
161        // NOTE: When adding a new initial cache, consider adjusting `reread_values_and_clear_caches()` as well.
162        Ok(Cache {
163            resolved: config.into(),
164            use_multi_pack_index,
165            object_hash,
166            #[cfg(feature = "revision")]
167            object_kind_hint,
168            static_pack_cache_limit_bytes,
169            pack_cache_bytes,
170            object_cache_bytes,
171            reflog,
172            refs_namespace,
173            is_bare,
174            ignore_case,
175            hex_len,
176            filter_config_section,
177            environment,
178            lenient_config,
179            attributes,
180            user_agent: Default::default(),
181            personas: Default::default(),
182            url_rewrite: Default::default(),
183            #[cfg(feature = "blob-diff")]
184            diff_renames: Default::default(),
185            #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
186            url_scheme: Default::default(),
187            #[cfg(feature = "blob-diff")]
188            diff_algorithm: Default::default(),
189        })
190    }
191
192    /// Call this with new `config` to update values and clear caches. Note that none of the values will be applied if a single
193    /// one is invalid.
194    /// However, those that are lazily read won't be re-evaluated right away and might thus pass now but fail later.
195    ///
196    /// Note that we unconditionally re-read all values.
197    pub fn reread_values_and_clear_caches_replacing_config(&mut self, config: crate::Config) -> Result<(), Error> {
198        let prev = std::mem::replace(&mut self.resolved, config);
199        match self.reread_values_and_clear_caches() {
200            Err(err) => {
201                drop(std::mem::replace(&mut self.resolved, prev));
202                Err(err)
203            }
204            Ok(()) => Ok(()),
205        }
206    }
207
208    /// Similar to `reread_values_and_clear_caches_replacing_config()`, but works on the existing configuration instead of a passed
209    /// in one that it them makes the default.
210    pub fn reread_values_and_clear_caches(&mut self) -> Result<(), Error> {
211        let config = &self.resolved;
212        let hex_len = util::parse_core_abbrev(config, self.object_hash).with_leniency(self.lenient_config)?;
213
214        use util::config_bool;
215        let ignore_case = config_bool(
216            config,
217            &Core::IGNORE_CASE,
218            "core.ignoreCase",
219            false,
220            self.lenient_config,
221        )?;
222
223        #[cfg(feature = "revision")]
224        {
225            let object_kind_hint = util::disambiguate_hint(config, self.lenient_config)?;
226            self.object_kind_hint = object_kind_hint;
227        }
228        let reflog = util::query_refupdates(config, self.lenient_config)?;
229        let refs_namespace = util::query_refs_namespace(config, self.lenient_config)?;
230
231        self.hex_len = hex_len;
232        self.ignore_case = ignore_case;
233        self.reflog = reflog;
234        self.refs_namespace = refs_namespace;
235
236        self.user_agent = Default::default();
237        self.personas = Default::default();
238        self.url_rewrite = Default::default();
239        #[cfg(feature = "blob-diff")]
240        {
241            self.diff_renames = Default::default();
242            self.diff_algorithm = Default::default();
243        }
244        (
245            self.static_pack_cache_limit_bytes,
246            self.pack_cache_bytes,
247            self.object_cache_bytes,
248        ) = util::parse_object_caches(config, self.lenient_config, self.filter_config_section)?;
249        #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
250        {
251            self.url_scheme = Default::default();
252        }
253
254        Ok(())
255    }
256
257    pub(crate) fn make_source_env(
258        crate::open::permissions::Environment {
259            xdg_config_home,
260            git_prefix,
261            home,
262            ..
263        }: open::permissions::Environment,
264    ) -> impl FnMut(&str) -> Option<OsString> {
265        move |name| {
266            match name {
267                git_ if git_.starts_with("GIT_") => Some(git_prefix),
268                "XDG_CONFIG_HOME" => Some(xdg_config_home),
269                "HOME" => {
270                    return if home.is_allowed() {
271                        gix_path::env::home_dir().map(Into::into)
272                    } else {
273                        None
274                    }
275                }
276                _ => None,
277            }
278            .and_then(|perm| perm.check_opt(name).and_then(gix_path::env::var))
279        }
280    }
281}
282
283impl crate::Repository {
284    /// Replace our own configuration with `config` and re-read all cached values, and apply them to select in-memory instances.
285    pub(crate) fn reread_values_and_clear_caches_replacing_config(
286        &mut self,
287        config: crate::Config,
288    ) -> Result<(), Error> {
289        let (a, b, c) = (
290            self.config.static_pack_cache_limit_bytes,
291            self.config.pack_cache_bytes,
292            self.config.object_cache_bytes,
293        );
294        self.config.reread_values_and_clear_caches_replacing_config(config)?;
295        self.apply_changed_values();
296        if a != self.config.static_pack_cache_limit_bytes
297            || b != self.config.pack_cache_bytes
298            || c != self.config.object_cache_bytes
299        {
300            setup_objects(&mut self.objects, &self.config);
301        }
302        Ok(())
303    }
304
305    fn apply_changed_values(&mut self) {
306        self.refs.write_reflog = util::reflog_or_default(self.config.reflog, self.workdir().is_some());
307        self.refs.namespace.clone_from(&self.config.refs_namespace);
308    }
309}
310
311fn apply_environment_overrides(
312    config: &mut gix_config::File<'static>,
313    git_prefix: Permission,
314    http_transport: Permission,
315    identity: Permission,
316    objects: Permission,
317) -> Result<(), Error> {
318    fn env(key: &'static dyn config::tree::Key) -> &'static str {
319        key.the_environment_override()
320    }
321    fn var_as_bstring(var: &str, perm: Permission) -> Option<BString> {
322        perm.check_opt(var)
323            .and_then(std::env::var_os)
324            .and_then(|val| gix_path::os_string_into_bstring(val).ok())
325    }
326
327    let mut env_override = gix_config::File::new(gix_config::file::Metadata::from(gix_config::Source::EnvOverride));
328    for (section_name, subsection_name, permission, data) in [
329        (
330            "core",
331            None,
332            git_prefix,
333            &[{
334                let key = &Core::WORKTREE;
335                (env(key), key.name)
336            }][..],
337        ),
338        (
339            "http",
340            None,
341            http_transport,
342            &[
343                ("GIT_HTTP_LOW_SPEED_LIMIT", "lowSpeedLimit"),
344                ("GIT_HTTP_LOW_SPEED_TIME", "lowSpeedTime"),
345                ("GIT_HTTP_USER_AGENT", "userAgent"),
346                {
347                    let key = &Http::SSL_CA_INFO;
348                    (env(key), key.name)
349                },
350                {
351                    let key = &Http::SSL_VERSION;
352                    (env(key), key.name)
353                },
354            ][..],
355        ),
356        (
357            "gitoxide",
358            None,
359            git_prefix,
360            &[
361                {
362                    let key = &Gitoxide::TRACE_PACKET;
363                    (env(key), key.name)
364                },
365                {
366                    let key = &Gitoxide::PARSE_PRECIOUS;
367                    (env(key), key.name)
368                },
369            ],
370        ),
371        (
372            "gitoxide",
373            Some(Cow::Borrowed("https".into())),
374            http_transport,
375            &[
376                ("HTTPS_PROXY", gitoxide::Https::PROXY.name),
377                ("https_proxy", gitoxide::Https::PROXY.name),
378            ],
379        ),
380        (
381            "gitoxide",
382            Some(Cow::Borrowed("http".into())),
383            http_transport,
384            &[
385                ("ALL_PROXY", "allProxy"),
386                {
387                    let key = &gitoxide::Http::ALL_PROXY;
388                    (env(key), key.name)
389                },
390                ("NO_PROXY", "noProxy"),
391                {
392                    let key = &gitoxide::Http::NO_PROXY;
393                    (env(key), key.name)
394                },
395                {
396                    let key = &gitoxide::Http::PROXY;
397                    (env(key), key.name)
398                },
399                {
400                    let key = &gitoxide::Http::VERBOSE;
401                    (env(key), key.name)
402                },
403                {
404                    let key = &gitoxide::Http::PROXY_AUTH_METHOD;
405                    (env(key), key.name)
406                },
407            ],
408        ),
409        (
410            "gitoxide",
411            Some(Cow::Borrowed("http".into())),
412            git_prefix,
413            &[{
414                let key = &gitoxide::Http::SSL_NO_VERIFY;
415                (env(key), key.name)
416            }],
417        ),
418        (
419            "gitoxide",
420            Some(Cow::Borrowed("credentials".into())),
421            git_prefix,
422            &[
423                {
424                    let key = &gitoxide::Credentials::TERMINAL_PROMPT;
425                    (env(key), key.name)
426                },
427                {
428                    let key = &gitoxide::Credentials::HELPER_STDERR;
429                    (env(key), key.name)
430                },
431            ],
432        ),
433        (
434            "gitoxide",
435            Some(Cow::Borrowed("committer".into())),
436            identity,
437            &[
438                {
439                    let key = &gitoxide::Committer::NAME_FALLBACK;
440                    (env(key), key.name)
441                },
442                {
443                    let key = &gitoxide::Committer::EMAIL_FALLBACK;
444                    (env(key), key.name)
445                },
446            ],
447        ),
448        (
449            "gitoxide",
450            Some(Cow::Borrowed("core".into())),
451            git_prefix,
452            &[
453                {
454                    let key = &gitoxide::Core::SHALLOW_FILE;
455                    (env(key), key.name)
456                },
457                {
458                    let key = &gitoxide::Core::REFS_NAMESPACE;
459                    (env(key), key.name)
460                },
461                {
462                    let key = &gitoxide::Core::EXTERNAL_COMMAND_STDERR;
463                    (env(key), key.name)
464                },
465            ],
466        ),
467        (
468            "gitoxide",
469            Some(Cow::Borrowed("author".into())),
470            identity,
471            &[
472                {
473                    let key = &gitoxide::Author::NAME_FALLBACK;
474                    (env(key), key.name)
475                },
476                {
477                    let key = &gitoxide::Author::EMAIL_FALLBACK;
478                    (env(key), key.name)
479                },
480            ],
481        ),
482        (
483            "gitoxide",
484            Some(Cow::Borrowed("commit".into())),
485            git_prefix,
486            &[
487                {
488                    let key = &gitoxide::Commit::COMMITTER_DATE;
489                    (env(key), key.name)
490                },
491                {
492                    let key = &gitoxide::Commit::AUTHOR_DATE;
493                    (env(key), key.name)
494                },
495            ],
496        ),
497        (
498            "gitoxide",
499            Some(Cow::Borrowed("allow".into())),
500            http_transport,
501            &[("GIT_PROTOCOL_FROM_USER", "protocolFromUser")],
502        ),
503        (
504            "gitoxide",
505            Some(Cow::Borrowed("user".into())),
506            identity,
507            &[{
508                let key = &gitoxide::User::EMAIL_FALLBACK;
509                (env(key), key.name)
510            }],
511        ),
512        (
513            "gitoxide",
514            Some(Cow::Borrowed("objects".into())),
515            objects,
516            &[
517                {
518                    let key = &gitoxide::Objects::REPLACE_REF_BASE;
519                    (env(key), key.name)
520                },
521                {
522                    let key = &gitoxide::Objects::CACHE_LIMIT;
523                    (env(key), key.name)
524                },
525            ],
526        ),
527        (
528            "gitoxide",
529            Some(Cow::Borrowed("ssh".into())),
530            git_prefix,
531            &[{
532                let key = &gitoxide::Ssh::COMMAND_WITHOUT_SHELL_FALLBACK;
533                (env(key), key.name)
534            }],
535        ),
536        (
537            "gitoxide",
538            Some(Cow::Borrowed("pathspec".into())),
539            git_prefix,
540            &[
541                {
542                    let key = &gitoxide::Pathspec::LITERAL;
543                    (env(key), key.name)
544                },
545                {
546                    let key = &gitoxide::Pathspec::GLOB;
547                    (env(key), key.name)
548                },
549                {
550                    let key = &gitoxide::Pathspec::NOGLOB;
551                    (env(key), key.name)
552                },
553                {
554                    let key = &gitoxide::Pathspec::ICASE;
555                    (env(key), key.name)
556                },
557            ],
558        ),
559        (
560            "ssh",
561            None,
562            git_prefix,
563            &[{
564                let key = &config::tree::Ssh::VARIANT;
565                (env(key), key.name)
566            }],
567        ),
568        #[cfg(feature = "blob-diff")]
569        (
570            "diff",
571            None,
572            git_prefix,
573            &[{
574                let key = &config::tree::Diff::EXTERNAL;
575                (env(key), key.name)
576            }],
577        ),
578    ] {
579        let mut section = env_override
580            .new_section(section_name, subsection_name)
581            .expect("statically known valid section name");
582        for (var, key) in data {
583            if let Some(value) = var_as_bstring(var, permission) {
584                section.push_with_comment(
585                    (*key).try_into().expect("statically known to be valid"),
586                    Some(value.as_ref()),
587                    format!("from {var}").as_str(),
588                );
589            }
590        }
591        if section.num_values() == 0 {
592            let id = section.id();
593            env_override.remove_section_by_id(id);
594        }
595    }
596
597    {
598        let mut section = env_override
599            .new_section("core", None)
600            .expect("statically known valid section name");
601
602        for (var, key, permission) in [
603            {
604                let key = &Core::DELTA_BASE_CACHE_LIMIT;
605                (env(key), key.name, objects)
606            },
607            {
608                let key = &Core::SSH_COMMAND;
609                (env(key), key.name, git_prefix)
610            },
611            {
612                let key = &Core::USE_REPLACE_REFS;
613                (env(key), key.name, objects)
614            },
615        ] {
616            if let Some(value) = var_as_bstring(var, permission) {
617                section.push_with_comment(
618                    key.try_into().expect("statically known to be valid"),
619                    Some(value.as_ref()),
620                    format!("from {var}").as_str(),
621                );
622            }
623        }
624
625        if section.num_values() == 0 {
626            let id = section.id();
627            env_override.remove_section_by_id(id);
628        }
629    }
630
631    if !env_override.is_void() {
632        config.append(env_override);
633    }
634    Ok(())
635}