Skip to content

Commit 4a7eb40

Browse files
committed
apply transport related environment variables as config overrides
Using git-configuration to store overrides from the environment is helpful as it more tighly integrates with the best configuration system there is, and also is nicely visualizable.
1 parent 9793511 commit 4a7eb40

File tree

13 files changed

+249
-97
lines changed

13 files changed

+249
-97
lines changed

git-repository/src/clone/fetch/util.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,13 @@ pub fn replace_changed_local_config_file(repo: &mut Repository, mut config: git_
3838
for id in ids_to_remove {
3939
repo_config.remove_section_by_id(id);
4040
}
41-
crate::config::overrides::append(&mut config, &repo.options.api_config_overrides, git_config::Source::Api)
42-
.expect("applied once and can be applied again");
41+
crate::config::overrides::append(
42+
&mut config,
43+
&repo.options.api_config_overrides,
44+
git_config::Source::Api,
45+
|_| None,
46+
)
47+
.expect("applied once and can be applied again");
4348
repo_config.append(config);
4449
repo.reread_values_and_clear_caches()
4550
.expect("values could be read once and can be read again");

git-repository/src/config/cache/access.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl Cache {
5050
.user_agent
5151
.get_or_init(|| {
5252
self.resolved
53-
.string("gitoxide", None, "userAgent")
53+
.string_by_key("gitoxide.userAgent")
5454
.map(|s| s.to_string())
5555
.unwrap_or_else(|| crate::env::agent().into())
5656
})

git-repository/src/config/cache/init.rs

+135-75
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use crate::{
44
config::{cache::util::ApplyLeniency, Cache},
55
repository,
66
};
7+
use git_config::File;
8+
use git_sec::Permission;
9+
use std::borrow::Cow;
710

811
/// Initialization
912
impl Cache {
@@ -55,82 +58,82 @@ impl Cache {
5558
..util::base_options(lossy)
5659
};
5760

58-
let config =
59-
{
60-
let home_env = &home_env;
61-
let xdg_config_home_env = &xdg_config_home_env;
62-
let git_prefix = &git_prefix;
63-
let metas = [
64-
git_config::source::Kind::GitInstallation,
65-
git_config::source::Kind::System,
66-
git_config::source::Kind::Global,
67-
]
68-
.iter()
69-
.flat_map(|kind| kind.sources())
70-
.filter_map(|source| {
71-
match source {
72-
git_config::Source::GitInstallation if !use_installation => return None,
73-
git_config::Source::System if !use_system => return None,
74-
git_config::Source::Git if !use_git => return None,
75-
git_config::Source::User if !use_user => return None,
76-
_ => {}
77-
}
78-
source
79-
.storage_location(&mut |name| {
80-
match name {
81-
git_ if git_.starts_with("GIT_") => Some(git_prefix),
82-
"XDG_CONFIG_HOME" => Some(xdg_config_home_env),
83-
"HOME" => Some(home_env),
84-
_ => None,
85-
}
86-
.and_then(|perm| std::env::var_os(name).and_then(|val| perm.check_opt(val)))
87-
})
88-
.map(|p| (source, p.into_owned()))
89-
})
90-
.map(|(source, path)| git_config::file::Metadata {
91-
path: Some(path),
92-
source: *source,
93-
level: 0,
94-
trust: git_sec::Trust::Full,
95-
});
96-
97-
let err_on_nonexisting_paths = false;
98-
let mut globals = git_config::File::from_paths_metadata_buf(
99-
metas,
100-
&mut buf,
101-
err_on_nonexisting_paths,
102-
git_config::file::init::Options {
103-
includes: git_config::file::includes::Options::no_follow(),
104-
..options
105-
},
106-
)
107-
.map_err(|err| match err {
108-
git_config::file::init::from_paths::Error::Init(err) => Error::from(err),
109-
git_config::file::init::from_paths::Error::Io(err) => err.into(),
110-
})?
111-
.unwrap_or_default();
112-
113-
globals.append(git_dir_config);
114-
globals.resolve_includes(options)?;
115-
if use_env {
116-
globals.append(git_config::File::from_env(options)?.unwrap_or_default());
117-
}
118-
if !cli_config_overrides.is_empty() {
119-
crate::config::overrides::append(&mut globals, cli_config_overrides, git_config::Source::Cli)
120-
.map_err(|err| Error::ConfigOverrides {
121-
err,
122-
source: git_config::Source::Cli,
123-
})?;
124-
}
125-
if !api_config_overrides.is_empty() {
126-
crate::config::overrides::append(&mut globals, api_config_overrides, git_config::Source::Api)
127-
.map_err(|err| Error::ConfigOverrides {
128-
err,
129-
source: git_config::Source::Api,
130-
})?;
61+
let config = {
62+
let home_env = &home_env;
63+
let xdg_config_home_env = &xdg_config_home_env;
64+
let git_prefix = &git_prefix;
65+
let metas = [
66+
git_config::source::Kind::GitInstallation,
67+
git_config::source::Kind::System,
68+
git_config::source::Kind::Global,
69+
]
70+
.iter()
71+
.flat_map(|kind| kind.sources())
72+
.filter_map(|source| {
73+
match source {
74+
git_config::Source::GitInstallation if !use_installation => return None,
75+
git_config::Source::System if !use_system => return None,
76+
git_config::Source::Git if !use_git => return None,
77+
git_config::Source::User if !use_user => return None,
78+
_ => {}
13179
}
132-
globals
133-
};
80+
source
81+
.storage_location(&mut |name| {
82+
match name {
83+
git_ if git_.starts_with("GIT_") => Some(git_prefix),
84+
"XDG_CONFIG_HOME" => Some(xdg_config_home_env),
85+
"HOME" => Some(home_env),
86+
_ => None,
87+
}
88+
.and_then(|perm| std::env::var_os(name).and_then(|val| perm.check_opt(val)))
89+
})
90+
.map(|p| (source, p.into_owned()))
91+
})
92+
.map(|(source, path)| git_config::file::Metadata {
93+
path: Some(path),
94+
source: *source,
95+
level: 0,
96+
trust: git_sec::Trust::Full,
97+
});
98+
99+
let err_on_nonexisting_paths = false;
100+
let mut globals = git_config::File::from_paths_metadata_buf(
101+
metas,
102+
&mut buf,
103+
err_on_nonexisting_paths,
104+
git_config::file::init::Options {
105+
includes: git_config::file::includes::Options::no_follow(),
106+
..options
107+
},
108+
)
109+
.map_err(|err| match err {
110+
git_config::file::init::from_paths::Error::Init(err) => Error::from(err),
111+
git_config::file::init::from_paths::Error::Io(err) => err.into(),
112+
})?
113+
.unwrap_or_default();
114+
115+
globals.append(git_dir_config);
116+
globals.resolve_includes(options)?;
117+
if use_env {
118+
globals.append(git_config::File::from_env(options)?.unwrap_or_default());
119+
}
120+
if !cli_config_overrides.is_empty() {
121+
crate::config::overrides::append(&mut globals, cli_config_overrides, git_config::Source::Cli, |_| None)
122+
.map_err(|err| Error::ConfigOverrides {
123+
err,
124+
source: git_config::Source::Cli,
125+
})?;
126+
}
127+
if !api_config_overrides.is_empty() {
128+
crate::config::overrides::append(&mut globals, api_config_overrides, git_config::Source::Api, |_| None)
129+
.map_err(|err| Error::ConfigOverrides {
130+
err,
131+
source: git_config::Source::Api,
132+
})?;
133+
}
134+
apply_environment_overrides(&mut globals, *git_prefix)?;
135+
globals
136+
};
134137

135138
let hex_len = util::parse_core_abbrev(&config, object_hash).with_leniency(lenient_config)?;
136139

@@ -207,3 +210,60 @@ impl Cache {
207210
Ok(())
208211
}
209212
}
213+
214+
fn apply_environment_overrides(config: &mut File<'static>, git_prefix: Permission) -> Result<(), Error> {
215+
fn var_as_bstring(var: &str, perm: Permission) -> Option<BString> {
216+
perm.check_opt(var)
217+
.and_then(std::env::var_os)
218+
.and_then(|val| git_path::os_string_into_bstring(val).ok())
219+
}
220+
221+
let mut env_override = git_config::File::new(git_config::file::Metadata::from(git_config::Source::EnvOverride));
222+
{
223+
let mut http = env_override
224+
.new_section("http", None)
225+
.expect("statically known valid section name");
226+
for (var, key) in [
227+
("GIT_HTTP_LOW_SPEED_LIMIT", "lowSpeedLimit"),
228+
("GIT_HTTP_LOW_SPEED_TIME", "lowSpeedTime"),
229+
("GIT_HTTP_USER_AGENT", "userAgent"),
230+
("GIT_HTTP_PROXY_AUTHMETHOD", "proxyAuthMethod"),
231+
] {
232+
if let Some(value) = var_as_bstring(var, git_prefix) {
233+
http.push_with_comment(
234+
key.try_into().expect("statically known to be valid"),
235+
Some(value.as_ref()),
236+
format!("from {var}").as_str(),
237+
);
238+
}
239+
}
240+
if http.is_void() {
241+
env_override.remove_section("http", None); // TODO: use _by_id
242+
}
243+
}
244+
245+
{
246+
let mut https = env_override
247+
.new_section("gitoxide", Some(Cow::Borrowed("https".into())))
248+
.expect("statically known valid section name");
249+
250+
for (var, key) in [("HTTPS_PROXY", "proxy"), ("https_proxy", "proxy")] {
251+
if let Some(value) = var_as_bstring(var, git_prefix) {
252+
https.push_with_comment(
253+
key.try_into().expect("statically known to be valid"),
254+
Some(value.as_ref()),
255+
format!("from {var}").as_str(),
256+
);
257+
}
258+
}
259+
260+
if https.is_void() {
261+
env_override.remove_section("gitoxide", Some("https".into())); // TODO: use _by_id
262+
}
263+
}
264+
265+
if !env_override.is_void() {
266+
config.append(env_override);
267+
}
268+
Ok(())
269+
}

git-repository/src/config/overrides.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::convert::TryFrom;
22

33
use crate::bstr::{BStr, BString, ByteSlice};
44

5-
/// The error returned by [SnapshotMut::apply_cli_overrides()][crate::config::SnapshotMut::apply_cli_overrides()].
5+
/// The error returned by [SnapshotMut::apply_cli_overrides()][crate::config::SnapshotMut::append_config()].
66
#[derive(Debug, thiserror::Error)]
77
#[allow(missing_docs)]
88
pub enum Error {
@@ -21,6 +21,7 @@ pub(crate) fn append(
2121
config: &mut git_config::File<'static>,
2222
values: impl IntoIterator<Item = impl AsRef<BStr>>,
2323
source: git_config::Source,
24+
mut make_comment: impl FnMut(&BStr) -> Option<BString>,
2425
) -> Result<(), Error> {
2526
let mut file = git_config::File::new(git_config::file::Metadata::from(source));
2627
for key_value in values {
@@ -31,13 +32,17 @@ pub(crate) fn append(
3132
let key = git_config::parse::key(key.to_str().map_err(|_| Error::InvalidKey { input: key.into() })?)
3233
.ok_or_else(|| Error::InvalidKey { input: key.into() })?;
3334
let mut section = file.section_mut_or_create_new(key.section_name, key.subsection_name)?;
34-
section.push(
35+
let key =
3536
git_config::parse::section::Key::try_from(key.value_name.to_owned()).map_err(|err| Error::SectionKey {
3637
source: err,
3738
key: key.value_name.into(),
38-
})?,
39-
value.map(|v| v.as_bstr()),
40-
);
39+
})?;
40+
let comment = make_comment(key_value);
41+
let value = value.map(|v| v.as_bstr());
42+
match comment {
43+
Some(comment) => section.push_with_comment(key, value, &**comment),
44+
None => section.push(key, value),
45+
}
4146
}
4247
config.append(file);
4348
Ok(())

git-repository/src/config/snapshot/_impls.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl Deref for Snapshot<'_> {
5151
type Target = git_config::File<'static>;
5252

5353
fn deref(&self) -> &Self::Target {
54-
&self.plumbing()
54+
self.plumbing()
5555
}
5656
}
5757

git-repository/src/config/snapshot/access.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ impl<'repo> SnapshotMut<'repo> {
8686
values: impl IntoIterator<Item = impl AsRef<BStr>>,
8787
source: git_config::Source,
8888
) -> Result<&mut Self, crate::config::overrides::Error> {
89-
crate::config::overrides::append(&mut self.config, values, source)?;
89+
crate::config::overrides::append(&mut self.config, values, source, |v| Some(format!("-c {v}").into()))?;
9090
Ok(self)
9191
}
9292
/// Apply all changes made to this instance.

git-repository/src/open.rs

-1
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,6 @@ impl ThreadSafeRepository {
340340
// TODO: The following vars should end up as overrides of the respective configuration values (see git-config).
341341
// GIT_HTTP_PROXY_AUTHMETHOD, GIT_PROXY_SSL_CERT, GIT_PROXY_SSL_KEY, GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED.
342342
// GIT_PROXY_SSL_CAINFO, GIT_SSL_VERSION, GIT_SSL_CIPHER_LIST, GIT_HTTP_MAX_REQUESTS, GIT_CURL_FTP_NO_EPSV,
343-
// GIT_HTTP_LOW_SPEED_LIMIT, GIT_HTTP_LOW_SPEED_TIME, GIT_HTTP_USER_AGENT,
344343
// no_proxy, NO_PROXY, http_proxy, HTTPS_PROXY, https_proxy, ALL_PROXY, all_proxy
345344
pub fn open_with_environment_overrides(
346345
fallback_directory: impl Into<PathBuf>,

git-repository/src/repository/config/transport.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,8 @@ impl crate::Repository {
8888
where
8989
T: TryFrom<i64>,
9090
{
91-
let git_config::parse::Key {
92-
section_name,
93-
subsection_name,
94-
value_name,
95-
} = git_config::parse::key(key).expect("valid key statically known");
9691
config
97-
.integer_filter(section_name, subsection_name, value_name, &mut filter)
92+
.integer_filter_by_key(key, &mut filter)
9893
.transpose()
9994
.map_err(|err| crate::config::transport::Error::ConfigValue { source: err, key })
10095
.with_leniency(lenient)?
@@ -166,7 +161,7 @@ impl crate::Repository {
166161
opts.extra_headers = {
167162
let mut headers = Vec::new();
168163
for header in config
169-
.strings_filter("http", None, "extraHeader", &mut trusted_only)
164+
.strings_filter_by_key("http.extraHeader", &mut trusted_only)
170165
.unwrap_or_default()
171166
.into_iter()
172167
.map(|v| try_cow_to_string(v, lenient, Cow::Borrowed("http.extraHeader".into())))

git-repository/tests/repository/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod worktree;
1111
#[test]
1212
fn size_in_memory() {
1313
let actual_size = std::mem::size_of::<Repository>();
14-
let limit = 940;
14+
let limit = 1000;
1515
assert!(
1616
actual_size <= limit,
1717
"size of Repository shouldn't change without us noticing, it's meant to be cloned: should have been below {:?}, was {} (bigger on windows)",

0 commit comments

Comments
 (0)