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
19impl 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 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 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 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 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}