Skip to content

Commit 907b5aa

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 d0799d3 commit 907b5aa

File tree

9 files changed

+121
-17
lines changed

9 files changed

+121
-17
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::apply(&mut config, &repo.options.config_overrides, git_config::Source::Api)
42-
.expect("applied once and can be applied again");
41+
crate::config::overrides::apply(
42+
&mut config,
43+
&repo.options.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/init.rs

+33-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use crate::{
44
config::{cache::util::ApplyLeniency, Cache},
55
repository,
66
};
7+
use git_config::File;
8+
use git_sec::Permission;
79

810
/// Initialization
911
impl Cache {
@@ -114,8 +116,9 @@ impl Cache {
114116
globals.append(git_config::File::from_env(options)?.unwrap_or_default());
115117
}
116118
if !config_overrides.is_empty() {
117-
crate::config::overrides::apply(&mut globals, config_overrides, git_config::Source::Api)?;
119+
crate::config::overrides::apply(&mut globals, config_overrides, git_config::Source::Api, |_| None)?;
118120
}
121+
apply_environment_overrides(&mut globals, *git_prefix)?;
119122
globals
120123
};
121124

@@ -194,3 +197,32 @@ impl Cache {
194197
Ok(())
195198
}
196199
}
200+
201+
fn apply_environment_overrides(config: &mut File<'static>, git_prefix: Permission) -> Result<(), Error> {
202+
let mut env_override = git_config::File::new(git_config::file::Metadata::from(git_config::Source::EnvOverride));
203+
let mut section = env_override
204+
.new_section("http", None)
205+
.expect("statically known valid section name");
206+
for (var, key) in [
207+
("GIT_HTTP_LOW_SPEED_LIMIT", "lowSpeedLimit"),
208+
("GIT_HTTP_LOW_SPEED_TIME", "lowSpeedTime"),
209+
("GIT_HTTP_USER_AGENT", "userAgent"),
210+
] {
211+
if let Some(value) = git_prefix
212+
.check_opt(var)
213+
.and_then(std::env::var_os)
214+
.and_then(|val| git_path::os_string_into_bstring(val).ok())
215+
{
216+
section.push_with_comment(
217+
key.try_into().expect("statically known to be valid"),
218+
Some(value.as_ref()),
219+
format!("from {var}").as_str(),
220+
);
221+
}
222+
}
223+
224+
if !section.is_void() {
225+
config.append(env_override);
226+
}
227+
Ok(())
228+
}

git-repository/src/config/overrides.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub(crate) fn apply(
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 apply(
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/access.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ impl<'repo> SnapshotMut<'repo> {
9494
&mut self,
9595
values: impl IntoIterator<Item = impl AsRef<BStr>>,
9696
) -> Result<&mut Self, crate::config::overrides::Error> {
97-
crate::config::overrides::apply(&mut self.config, values, git_config::Source::Cli)?;
97+
crate::config::overrides::apply(&mut self.config, values, git_config::Source::Cli, |v| {
98+
Some(format!("-c {v}").into())
99+
})?;
98100
Ok(self)
99101
}
100102
/// Apply all changes made to this instance.

git-repository/src/open.rs

-1
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,6 @@ impl ThreadSafeRepository {
320320
// TODO: The following vars should end up as overrides of the respective configuration values (see git-config).
321321
// GIT_HTTP_PROXY_AUTHMETHOD, GIT_PROXY_SSL_CERT, GIT_PROXY_SSL_KEY, GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED.
322322
// GIT_PROXY_SSL_CAINFO, GIT_SSL_VERSION, GIT_SSL_CIPHER_LIST, GIT_HTTP_MAX_REQUESTS, GIT_CURL_FTP_NO_EPSV,
323-
// GIT_HTTP_LOW_SPEED_LIMIT, GIT_HTTP_LOW_SPEED_TIME, GIT_HTTP_USER_AGENT,
324323
// no_proxy, NO_PROXY, http_proxy, HTTPS_PROXY, https_proxy, ALL_PROXY, all_proxy
325324
pub fn open_with_environment_overrides(
326325
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/open.rs

+61
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,64 @@ mod submodules {
4444
.to_thread_local())
4545
}
4646
}
47+
48+
mod with_overrides {
49+
use crate::util::named_subrepo_opts;
50+
use git_object::bstr::BStr;
51+
use git_repository as git;
52+
use git_sec::Permission;
53+
use git_testtools::Env;
54+
use serial_test::serial;
55+
use std::borrow::Cow;
56+
57+
#[test]
58+
#[serial]
59+
fn order_from_api_and_cli_and_environment() -> crate::Result {
60+
let _env = Env::new()
61+
.set("GIT_HTTP_USER_AGENT", "agent from env")
62+
.set("GIT_HTTP_LOW_SPEED_LIMIT", "1")
63+
.set("GIT_HTTP_LOW_SPEED_TIME", "1");
64+
let mut opts = git::open::Options::isolated().config_overrides([
65+
"http.userAgent=agent-from-api",
66+
"http.lowSpeedLimit=2",
67+
"http.lowSpeedTime=2",
68+
]);
69+
opts.permissions.env.git_prefix = Permission::Allow;
70+
let mut repo = named_subrepo_opts("make_config_repos.sh", "http-config", opts)?;
71+
repo.config_snapshot_mut().apply_cli_overrides([
72+
"http.userAgent=agent-from-cli",
73+
"http.lowSpeedLimit=3",
74+
"http.lowSpeedTime=3",
75+
])?;
76+
let snapshot = repo.config_snapshot();
77+
let config = snapshot.plumbing();
78+
assert_eq!(
79+
config.strings_by_key("http.userAgent").expect("at least one value"),
80+
[
81+
cow_bstr("agentJustForHttp"),
82+
cow_bstr("agent-from-api"),
83+
cow_bstr("agent from env"),
84+
cow_bstr("agent-from-cli")
85+
]
86+
);
87+
assert_eq!(
88+
config
89+
.integers_by_key("http.lowSpeedLimit")
90+
.transpose()?
91+
.expect("many values"),
92+
[5120, 2, 1, 3]
93+
);
94+
assert_eq!(
95+
config
96+
.integers_by_key("http.lowSpeedTime")
97+
.transpose()?
98+
.expect("many values"),
99+
[10, 2, 1, 3]
100+
);
101+
Ok(())
102+
}
103+
104+
fn cow_bstr(s: &str) -> Cow<BStr> {
105+
Cow::Borrowed(s.into())
106+
}
107+
}

git-repository/tests/util/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ pub fn named_repo(name: &str) -> Result<Repository> {
2323
Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?.to_thread_local())
2424
}
2525

26+
pub fn named_subrepo_opts(fixture: &str, name: &str, opts: open::Options) -> Result<Repository> {
27+
let repo_path = git_testtools::scripted_fixture_repo_read_only(fixture)?.join(name);
28+
Ok(ThreadSafeRepository::open_opts(repo_path, opts)?.to_thread_local())
29+
}
30+
2631
pub fn restricted() -> open::Options {
2732
open::Options::isolated()
2833
}

git-sec/src/permission.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl Permission {
4444
}
4545

4646
/// Like [`check()`][Self::check()], but degenerates the type to an option to make it more useful in cases where
47-
/// `Forbid` shoudn't abort the entire operation.
47+
/// `Forbid` shouldn't abort the entire operation.
4848
pub fn check_opt<R: std::fmt::Debug>(&self, resource: R) -> Option<R> {
4949
match self {
5050
Permission::Allow => Some(resource),

0 commit comments

Comments
 (0)