Skip to content

Commit 99c57c0

Browse files
committed
fix(config): Don't merge unmergable config
1 parent 1948c90 commit 99c57c0

File tree

5 files changed

+67
-42
lines changed

5 files changed

+67
-42
lines changed

src/cargo/util/context/de.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -154,17 +154,13 @@ impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> {
154154
where
155155
V: de::Visitor<'de>,
156156
{
157-
let merge = if name == "StringList" {
158-
true
159-
} else if name == "UnmergedStringList" {
160-
false
157+
if name == "StringList" {
158+
let vals = self.gctx.get_list_or_string(&self.key)?;
159+
let vals: Vec<String> = vals.into_iter().map(|vd| vd.0).collect();
160+
visitor.visit_newtype_struct(vals.into_deserializer())
161161
} else {
162-
return visitor.visit_newtype_struct(self);
163-
};
164-
165-
let vals = self.gctx.get_list_or_string(&self.key, merge)?;
166-
let vals: Vec<String> = vals.into_iter().map(|vd| vd.0).collect();
167-
visitor.visit_newtype_struct(vals.into_deserializer())
162+
visitor.visit_newtype_struct(self)
163+
}
168164
}
169165

170166
fn deserialize_enum<V>(

src/cargo/util/context/key.rs

+10
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ impl ConfigKey {
9393
pub fn is_root(&self) -> bool {
9494
self.parts.is_empty()
9595
}
96+
97+
/// Returns whether or not the given key string matches this key.
98+
/// Use * to match any key part.
99+
pub fn matches(&self, pattern: &str) -> bool {
100+
let mut parts = self.parts();
101+
pattern
102+
.split('.')
103+
.all(|pat| parts.next() == Some(pat) || pat == "*")
104+
&& parts.next().is_none()
105+
}
96106
}
97107

98108
impl fmt::Display for ConfigKey {

src/cargo/util/context/mod.rs

+43-28
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ impl GlobalContext {
736736
Ok(Some(CV::List(cv_list, cv_def)))
737737
}
738738
Some(cv) => {
739-
// This can't assume StringList or UnmergedStringList.
739+
// This can't assume StringList.
740740
// Return an error, which is the behavior of merging
741741
// multiple config.toml files with the same scenario.
742742
bail!(
@@ -910,21 +910,9 @@ impl GlobalContext {
910910
}
911911

912912
/// Helper for `StringList` type to get something that is a string or list.
913-
fn get_list_or_string(
914-
&self,
915-
key: &ConfigKey,
916-
merge: bool,
917-
) -> CargoResult<Vec<(String, Definition)>> {
913+
fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
918914
let mut res = Vec::new();
919915

920-
if !merge {
921-
self.get_env_list(key, &mut res)?;
922-
923-
if !res.is_empty() {
924-
return Ok(res);
925-
}
926-
}
927-
928916
match self.get_cv(key)? {
929917
Some(CV::List(val, _def)) => res.extend(val),
930918
Some(CV::String(val, def)) => {
@@ -943,6 +931,7 @@ impl GlobalContext {
943931
}
944932

945933
/// Internal method for getting an environment variable as a list.
934+
/// If the key is a non-mergable list and a value is found in the environment, existing values are cleared.
946935
fn get_env_list(
947936
&self,
948937
key: &ConfigKey,
@@ -953,6 +942,10 @@ impl GlobalContext {
953942
return Ok(());
954943
};
955944

945+
if is_nonmergable_list(&key) {
946+
output.clear();
947+
}
948+
956949
let def = Definition::Environment(key.as_env_key().to_string());
957950
if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
958951
// Parse an environment string as a TOML array.
@@ -2227,13 +2220,31 @@ impl ConfigValue {
22272220
///
22282221
/// Container and non-container types cannot be mixed.
22292222
fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2223+
self.merge_helper(from, force, &mut ConfigKey::new())
2224+
}
2225+
2226+
fn merge_helper(
2227+
&mut self,
2228+
from: ConfigValue,
2229+
force: bool,
2230+
parts: &mut ConfigKey,
2231+
) -> CargoResult<()> {
2232+
let is_higher_priority = from.definition().is_higher_priority(self.definition());
22302233
match (self, from) {
22312234
(&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2232-
if force {
2233-
old.append(new);
2235+
if is_nonmergable_list(&parts) {
2236+
// Use whichever list is higher priority.
2237+
if force || is_higher_priority {
2238+
mem::swap(new, old);
2239+
}
22342240
} else {
2235-
new.append(old);
2236-
mem::swap(new, old);
2241+
// Merge the lists together.
2242+
if force {
2243+
old.append(new);
2244+
} else {
2245+
new.append(old);
2246+
mem::swap(new, old);
2247+
}
22372248
}
22382249
old.sort_by(|a, b| a.1.cmp(&b.1));
22392250
}
@@ -2243,7 +2254,8 @@ impl ConfigValue {
22432254
Occupied(mut entry) => {
22442255
let new_def = value.definition().clone();
22452256
let entry = entry.get_mut();
2246-
entry.merge(value, force).with_context(|| {
2257+
parts.push(&key);
2258+
entry.merge_helper(value, force, parts).with_context(|| {
22472259
format!(
22482260
"failed to merge key `{}` between \
22492261
{} and {}",
@@ -2273,7 +2285,7 @@ impl ConfigValue {
22732285
));
22742286
}
22752287
(old, mut new) => {
2276-
if force || new.definition().is_higher_priority(old.definition()) {
2288+
if force || is_higher_priority {
22772289
mem::swap(old, &mut new);
22782290
}
22792291
}
@@ -2348,6 +2360,17 @@ impl ConfigValue {
23482360
}
23492361
}
23502362

2363+
/// List of which configuration lists cannot be merged.
2364+
/// Instead of merging, these the higher priority list replaces the lower priority list.
2365+
fn is_nonmergable_list(key: &ConfigKey) -> bool {
2366+
key.matches("registry.credential-provider")
2367+
|| key.matches("registries.*.credential-provider")
2368+
|| key.matches("target.*.runner")
2369+
|| key.matches("host.runner")
2370+
|| key.matches("credential-alias.*")
2371+
|| key.matches("doc.browser")
2372+
}
2373+
23512374
pub fn homedir(cwd: &Path) -> Option<PathBuf> {
23522375
::home::cargo_home_with_cwd(cwd).ok()
23532376
}
@@ -2916,14 +2939,6 @@ impl StringList {
29162939
}
29172940
}
29182941

2919-
/// Alternative to [`StringList`] that follows precedence rules, rather than merging config values with environment values,
2920-
///
2921-
/// e.g. a string list found in the environment will be used instead of one in a config file.
2922-
///
2923-
/// This is currently only used by [`PathAndArgs`]
2924-
#[derive(Debug, Deserialize)]
2925-
pub struct UnmergedStringList(Vec<String>);
2926-
29272942
#[macro_export]
29282943
macro_rules! __shell_print {
29292944
($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({

src/cargo/util/context/path.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{GlobalContext, UnmergedStringList, Value};
1+
use super::{GlobalContext, StringList, Value};
22
use serde::{de::Error, Deserialize};
33
use std::path::PathBuf;
44

@@ -53,6 +53,10 @@ impl ConfigRelativePath {
5353
///
5454
/// Typically you should use `ConfigRelativePath::resolve_program` on the path
5555
/// to get the actual program.
56+
///
57+
/// **Note**: Any usage of this type in config needs to be listed in
58+
/// the `util::context::is_nonmergable_list` check to prevent list merging
59+
/// from multiple config files.
5660
#[derive(Debug, Clone, PartialEq)]
5761
pub struct PathAndArgs {
5862
pub path: ConfigRelativePath,
@@ -64,7 +68,7 @@ impl<'de> serde::Deserialize<'de> for PathAndArgs {
6468
where
6569
D: serde::Deserializer<'de>,
6670
{
67-
let vsl = Value::<UnmergedStringList>::deserialize(deserializer)?;
71+
let vsl = Value::<StringList>::deserialize(deserializer)?;
6872
let mut strings = vsl.val.0;
6973
if strings.is_empty() {
7074
return Err(D::Error::invalid_length(0, &"at least one element"));

tests/testsuite/config.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2192,8 +2192,8 @@ credential-provider = ['c', 'd']
21922192
.unwrap()
21932193
.credential_provider
21942194
.unwrap();
2195-
assert_eq!(provider.path.raw_value(), "a");
2196-
assert_eq!(provider.args, ["b", "c", "d"]);
2195+
assert_eq!(provider.path.raw_value(), "c");
2196+
assert_eq!(provider.args, ["d"]);
21972197
}
21982198

21992199
#[cargo_test]

0 commit comments

Comments
 (0)