Skip to content

Commit fe5cab9

Browse files
committed
Allow remote overrides for http options
1 parent 1c2e755 commit fe5cab9

File tree

6 files changed

+140
-70
lines changed

6 files changed

+140
-70
lines changed

git-repository/src/config/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ pub mod checkout_options {
110110
///
111111
pub mod transport {
112112
use crate::bstr;
113+
use crate::bstr::BStr;
114+
use std::borrow::Cow;
113115

114116
/// The error produced when configuring a transport for a particular protocol.
115117
#[derive(Debug, thiserror::Error)]
@@ -130,7 +132,7 @@ pub mod transport {
130132
},
131133
#[error("Could not decode value at key {key:?} as UTF-8 string")]
132134
IllformedUtf8 {
133-
key: &'static str,
135+
key: Cow<'static, BStr>,
134136
source: bstr::FromUtf8Error,
135137
},
136138
#[error("Invalid URL passed for configuration")]
@@ -141,12 +143,15 @@ pub mod transport {
141143

142144
///
143145
pub mod http {
146+
use crate::bstr::BStr;
147+
use std::borrow::Cow;
148+
144149
/// The error produced when configuring a HTTP transport.
145150
#[derive(Debug, thiserror::Error)]
146151
#[allow(missing_docs)]
147152
pub enum Error {
148-
#[error("The proxy authentication method name {value:?} is invalid")]
149-
InvalidProxyAuthMethod { value: String },
153+
#[error("The proxy authentication method name {value:?} found at key `{key}` is invalid")]
154+
InvalidProxyAuthMethod { value: String, key: Cow<'static, BStr> },
150155
#[error("Could not configure the credential helpers for the authenticated proxy url")]
151156
ConfigureProxyAuthenticate(#[from] crate::config::snapshot::credential_helpers::Error),
152157
}

git-repository/src/remote/connection/ref_map.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,14 @@ where
161161
};
162162

163163
if self.transport_options.is_none() {
164-
self.transport_options =
165-
self.remote
166-
.repo
167-
.transport_options(url.as_ref())
168-
.map_err(|err| Error::GatherTransportConfig {
169-
source: err,
170-
url: url.into_owned(),
171-
})?;
164+
self.transport_options = self
165+
.remote
166+
.repo
167+
.transport_options(url.as_ref(), self.remote.name().map(|n| n.as_bstr()))
168+
.map_err(|err| Error::GatherTransportConfig {
169+
source: err,
170+
url: url.into_owned(),
171+
})?;
172172
}
173173
if let Some(config) = self.transport_options.as_ref() {
174174
self.transport.configure(&**config)?;

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

Lines changed: 90 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,18 @@ use crate::bstr::BStr;
55
impl crate::Repository {
66
/// Produce configuration suitable for `url`, as differentiated by its protocol/scheme, to be passed to a transport instance via
77
/// [configure()][git_transport::client::TransportWithoutIO::configure()] (via `&**config` to pass the contained `Any` and not the `Box`).
8-
/// `None` is returned if there is no known configuration.
8+
/// `None` is returned if there is no known configuration. If `remote_name` is not `None`, the remote's name may contribute to
9+
/// configuration overrides, typically for the HTTP transport.
910
///
1011
/// Note that the caller may cast the instance themselves to modify it before passing it on.
1112
///
12-
///
13-
// let (mut cascade, _action_with_normalized_url, prompt_opts) =
14-
// self.remote.repo.config_snapshot().credential_helpers(url)?;
15-
// Ok(Box::new(move |action| cascade.invoke(action, prompt_opts.clone())) as AuthenticateFn<'_>)
16-
/// For transports that support proxy authentication, the authentication
17-
/// [default authentication method](crate::config::Snapshot::credential_helpers()) will be used with the url of the proxy.
13+
/// For transports that support proxy authentication, the
14+
/// [default authentication method](crate::config::Snapshot::credential_helpers()) will be used with the url of the proxy
15+
/// if it contains a user name.
1816
pub fn transport_options<'a>(
1917
&self,
2018
url: impl Into<&'a BStr>,
19+
remote_name: Option<&BStr>,
2120
) -> Result<Option<Box<dyn Any>>, crate::config::transport::Error> {
2221
let url = git_url::parse(url.into())?;
2322
use git_url::Scheme::*;
@@ -49,12 +48,15 @@ impl crate::Repository {
4948
fn try_cow_to_string(
5049
v: Cow<'_, BStr>,
5150
lenient: bool,
52-
key: &'static str,
51+
key: impl Into<Cow<'static, BStr>>,
5352
) -> Result<Option<String>, crate::config::transport::Error> {
5453
Vec::from(v.into_owned())
5554
.into_string()
5655
.map(Some)
57-
.map_err(|err| crate::config::transport::Error::IllformedUtf8 { source: err, key })
56+
.map_err(|err| crate::config::transport::Error::IllformedUtf8 {
57+
source: err,
58+
key: key.into(),
59+
})
5860
.with_leniency(lenient)
5961
}
6062

@@ -71,6 +73,7 @@ impl crate::Repository {
7173
{
7274
Ok(integer_opt(config, lenient, key, kind, filter)?.unwrap_or(default))
7375
}
76+
7477
fn integer_opt<T>(
7578
config: &git_config::File<'static>,
7679
lenient: bool,
@@ -103,6 +106,55 @@ impl crate::Repository {
103106
.transpose()
104107
.with_leniency(lenient)
105108
}
109+
110+
fn proxy_auth_method(
111+
value_and_key: Option<(Cow<'_, BStr>, Cow<'static, BStr>)>,
112+
lenient: bool,
113+
) -> Result<ProxyAuthMethod, crate::config::transport::Error> {
114+
let value = value_and_key
115+
.and_then(|(v, k)| {
116+
try_cow_to_string(v, lenient, k.clone())
117+
.map(|v| v.map(|v| (v, k)))
118+
.transpose()
119+
})
120+
.transpose()?
121+
.map(|(method, key)| {
122+
Ok(match method.as_str() {
123+
"anyauth" => ProxyAuthMethod::AnyAuth,
124+
"basic" => ProxyAuthMethod::Basic,
125+
"digest" => ProxyAuthMethod::Digest,
126+
"negotiate" => ProxyAuthMethod::Negotiate,
127+
"ntlm" => ProxyAuthMethod::Ntlm,
128+
_ => {
129+
return Err(crate::config::transport::http::Error::InvalidProxyAuthMethod {
130+
value: method,
131+
key,
132+
})
133+
}
134+
})
135+
})
136+
.transpose()?
137+
.unwrap_or_default();
138+
Ok(value)
139+
}
140+
141+
fn proxy(
142+
value: Option<(Cow<'_, BStr>, Cow<'static, BStr>)>,
143+
lenient: bool,
144+
) -> Result<Option<String>, crate::config::transport::Error> {
145+
Ok(value
146+
.and_then(|(v, k)| try_cow_to_string(v, lenient, k.clone()).transpose())
147+
.transpose()?
148+
.map(|mut proxy| {
149+
if !proxy.trim().is_empty() && !proxy.contains("://") {
150+
proxy.insert_str(0, "http://");
151+
proxy
152+
} else {
153+
proxy
154+
}
155+
}))
156+
}
157+
106158
let mut opts = http::Options::default();
107159
let config = &self.config.resolved;
108160
let mut trusted_only = self.filter_config_section();
@@ -113,7 +165,7 @@ impl crate::Repository {
113165
.strings_filter("http", None, "extraHeader", &mut trusted_only)
114166
.unwrap_or_default()
115167
.into_iter()
116-
.map(|v| try_cow_to_string(v, lenient, "http.extraHeader"))
168+
.map(|v| try_cow_to_string(v, lenient, Cow::Borrowed("http.extraHeader".into())))
117169
{
118170
let header = header?;
119171
if let Some(header) = header {
@@ -149,38 +201,34 @@ impl crate::Repository {
149201
integer(config, lenient, "http.lowSpeedTime", "u64", trusted_only, 0)?;
150202
opts.low_speed_limit_bytes_per_second =
151203
integer(config, lenient, "http.lowSpeedLimit", "u32", trusted_only, 0)?;
152-
opts.proxy = config
153-
.string_filter("http", None, "proxy", &mut trusted_only)
154-
.and_then(|v| try_cow_to_string(v, lenient, "http.proxy").transpose())
155-
.transpose()?
156-
.map(|mut proxy| {
157-
if !proxy.trim().is_empty() && !proxy.contains("://") {
158-
proxy.insert_str(0, "http://");
159-
proxy
160-
} else {
161-
proxy
162-
}
163-
});
164-
opts.proxy_auth_method = config
165-
.string_filter("http", None, "proxyAuthMethod", &mut trusted_only)
166-
.and_then(|v| try_cow_to_string(v, lenient, "http.proxyAuthMethod").transpose())
167-
.transpose()?
168-
.map(|method| {
169-
Ok(match method.as_str() {
170-
"anyauth" => ProxyAuthMethod::AnyAuth,
171-
"basic" => ProxyAuthMethod::Basic,
172-
"digest" => ProxyAuthMethod::Digest,
173-
"negotiate" => ProxyAuthMethod::Negotiate,
174-
"ntlm" => ProxyAuthMethod::Ntlm,
175-
_ => {
176-
return Err(crate::config::transport::http::Error::InvalidProxyAuthMethod {
177-
value: method,
178-
})
179-
}
204+
opts.proxy = proxy(
205+
remote_name
206+
.and_then(|name| {
207+
config
208+
.string_filter("remote", Some(name), "proxy", &mut trusted_only)
209+
.map(|v| (v, Cow::Owned(format!("remote.{name}.proxy").into())))
180210
})
181-
})
182-
.transpose()?
183-
.unwrap_or_default();
211+
.or_else(|| {
212+
config
213+
.string_filter("http", None, "proxy", &mut trusted_only)
214+
.map(|v| (v, Cow::Borrowed("http.proxy".into())))
215+
}),
216+
lenient,
217+
)?;
218+
opts.proxy_auth_method = proxy_auth_method(
219+
remote_name
220+
.and_then(|name| {
221+
config
222+
.string_filter("remote", Some(name), "proxyAuthMethod", &mut trusted_only)
223+
.map(|v| (v, Cow::Owned(format!("remote.{name}.proxyAuthMethod").into())))
224+
})
225+
.or_else(|| {
226+
config
227+
.string_filter("http", None, "proxyAuthMethod", &mut trusted_only)
228+
.map(|v| (v, Cow::Borrowed("http.proxyAuthMethod".into())))
229+
}),
230+
lenient,
231+
)?;
184232
opts.proxy_authenticate = opts
185233
.proxy
186234
.as_deref()
@@ -202,7 +250,7 @@ impl crate::Repository {
202250
.map(std::time::Duration::from_millis);
203251
opts.user_agent = config
204252
.string_filter("http", None, "userAgent", &mut trusted_only)
205-
.and_then(|v| try_cow_to_string(v, lenient, "http.userAgent").transpose())
253+
.and_then(|v| try_cow_to_string(v, lenient, Cow::Borrowed("http.userAgent".into())).transpose())
206254
.transpose()?
207255
.or_else(|| Some(crate::env::agent().into()));
208256

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:495e3613b7ee9c5a02101dd307de24d9754e531e2840bedecfed00d55ea656af
3-
size 10252
2+
oid sha256:83f5270e487f0364e4a63f82cf4bfb5cb302bee744e9d3df521b7b3f850f3046
3+
size 10772

git-repository/tests/fixtures/make_config_repos.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ git init http-config
1616
git config gitoxide.http.connectTimeout 60k
1717
)
1818

19+
git clone --shared http-config http-remote-override
20+
(cd http-remote-override
21+
22+
git config http.proxy http://localhost:9090
23+
git config http.proxyAuthMethod basic
24+
25+
git config remote.origin.proxy overridden
26+
git config remote.origin.proxyAuthMethod negotiate
27+
)
28+
1929
git init http-proxy-empty
2030
(cd http-proxy-empty
2131
git config http.proxy localhost:9090

git-repository/tests/repository/config/transport_options.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,36 @@
44
))]
55
mod http {
66
use git_repository as git;
7+
use git_transport::client::http::options::{FollowRedirects, ProxyAuthMethod};
78

89
pub(crate) fn repo(name: &str) -> git::Repository {
910
let dir = git_testtools::scripted_fixture_repo_read_only("make_config_repos.sh").unwrap();
1011
git::open_opts(dir.join(name), git::open::Options::isolated()).unwrap()
1112
}
1213

13-
fn http_options(repo: &git::Repository) -> git_transport::client::http::Options {
14+
fn http_options(repo: &git::Repository, remote_name: Option<&str>) -> git_transport::client::http::Options {
1415
let opts = repo
15-
.transport_options("https://example.com/does/not/matter")
16+
.transport_options("https://example.com/does/not/matter", remote_name.map(Into::into))
1617
.expect("valid configuration")
1718
.expect("configuration available for http");
1819
opts.downcast_ref::<git_transport::client::http::Options>()
1920
.expect("http options have been created")
2021
.to_owned()
2122
}
2223

24+
#[test]
25+
fn remote_overrides() {
26+
let repo = repo("http-remote-override");
27+
let git_transport::client::http::Options {
28+
proxy,
29+
proxy_auth_method,
30+
..
31+
} = http_options(&repo, Some("origin"));
32+
33+
assert_eq!(proxy_auth_method, ProxyAuthMethod::Negotiate);
34+
assert_eq!(proxy.as_deref(), Some("http://overridden"));
35+
}
36+
2337
#[test]
2438
fn simple_configuration() {
2539
let repo = repo("http-config");
@@ -34,28 +48,21 @@ mod http {
3448
user_agent,
3549
connect_timeout,
3650
backend,
37-
} = http_options(&repo);
51+
} = http_options(&repo, None);
3852
assert_eq!(
3953
extra_headers,
4054
&["ExtraHeader: value2", "ExtraHeader: value3"],
4155
"it respects empty values to clear prior values"
4256
);
43-
assert_eq!(
44-
follow_redirects,
45-
git_transport::client::http::options::FollowRedirects::Initial
46-
);
57+
assert_eq!(follow_redirects, FollowRedirects::Initial);
4758
assert_eq!(low_speed_limit_bytes_per_second, 5120);
4859
assert_eq!(low_speed_time_seconds, 10);
4960
assert_eq!(proxy.as_deref(), Some("http://localhost:9090"),);
5061
assert!(
5162
proxy_authenticate.is_none(),
5263
"no username means no authentication required"
5364
);
54-
assert_eq!(
55-
proxy_auth_method,
56-
git_transport::client::http::options::ProxyAuthMethod::Basic,
57-
"TODO: implement auth"
58-
);
65+
assert_eq!(proxy_auth_method, ProxyAuthMethod::Basic, "TODO: implement auth");
5966
assert_eq!(user_agent.as_deref(), Some("agentJustForHttp"));
6067
assert_eq!(connect_timeout, Some(std::time::Duration::from_millis(60 * 1024)));
6168
assert!(
@@ -68,7 +75,7 @@ mod http {
6875
fn http_proxy_with_username() {
6976
let repo = repo("http-proxy-authenticated");
7077

71-
let opts = http_options(&repo);
78+
let opts = http_options(&repo, None);
7279
assert_eq!(
7380
opts.proxy.as_deref(),
7481
Some("http://user@localhost:9090"),
@@ -84,7 +91,7 @@ mod http {
8491
fn empty_proxy_string_turns_it_off() {
8592
let repo = repo("http-proxy-empty");
8693

87-
let opts = http_options(&repo);
94+
let opts = http_options(&repo, None);
8895
assert_eq!(
8996
opts.proxy.as_deref(),
9097
Some(""),
@@ -96,7 +103,7 @@ mod http {
96103
fn proxy_without_protocol_is_defaulted_to_http() {
97104
let repo = repo("http-proxy-auto-prefix");
98105

99-
let opts = http_options(&repo);
106+
let opts = http_options(&repo, None);
100107
assert_eq!(opts.proxy.as_deref(), Some("http://localhost:9090"));
101108
}
102109
}

0 commit comments

Comments
 (0)