Skip to content

Commit 964b730

Browse files
committed
Auto merge of #11756 - arlosi:sparse-uninstall, r=ehuss
Fix Cargo removing the sparse+ prefix from sparse URLs in .crates.toml The URL associated with a `SourceId` for a sparse registry includes the `sparse+` prefix in the URL to differentiate from git (non-sparse) registries. `SourceId::from_url` was not including the `sparse+` prefix of sparse registry URLs on construction, which caused roundtrips of `as_url` and `from_url` to fail by losing the prefix. Fixes #11751 by: * Including the prefix in the URL * Adding a test that verifies round-trip behavior for sparse `SourceId`s * Modifying `CanonicalUrl` so it no longer considers `sparse+` and non-`sparse+` URLs to be equivalent
2 parents d84b267 + ab726e7 commit 964b730

File tree

3 files changed

+85
-13
lines changed

3 files changed

+85
-13
lines changed

src/cargo/core/source/source_id.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ impl SourceId {
8181
///
8282
/// The canonical url will be calculated, but the precise field will not
8383
fn new(kind: SourceKind, url: Url, name: Option<&str>) -> CargoResult<SourceId> {
84+
if kind == SourceKind::SparseRegistry {
85+
// Sparse URLs are different because they store the kind prefix (sparse+)
86+
// in the URL. This is because the prefix is necessary to differentiate
87+
// from regular registries (git-based). The sparse+ prefix is included
88+
// everywhere, including user-facing locations such as the `config.toml`
89+
// file that defines the registry, or whenever Cargo displays it to the user.
90+
assert!(url.as_str().starts_with("sparse+"));
91+
}
8492
let source_id = SourceId::wrap(SourceIdInner {
8593
kind,
8694
canonical_url: CanonicalUrl::new(&url)?,
@@ -152,7 +160,7 @@ impl SourceId {
152160
.with_precise(Some("locked".to_string())))
153161
}
154162
"sparse" => {
155-
let url = url.into_url()?;
163+
let url = string.into_url()?;
156164
Ok(SourceId::new(SourceKind::SparseRegistry, url, None)?
157165
.with_precise(Some("locked".to_string())))
158166
}
@@ -721,6 +729,7 @@ impl<'a> fmt::Display for SourceIdAsUrl<'a> {
721729
ref url,
722730
..
723731
} => {
732+
// Sparse registry URL already includes the `sparse+` prefix
724733
write!(f, "{url}")
725734
}
726735
SourceIdInner {
@@ -864,4 +873,14 @@ mod tests {
864873
assert_eq!(gen_hash(source_id), 17459999773908528552);
865874
assert_eq!(crate::util::hex::short_hash(&source_id), "6568fe2c2fab5bfe");
866875
}
876+
877+
#[test]
878+
fn serde_roundtrip() {
879+
let url = "sparse+https://my-crates.io/".into_url().unwrap();
880+
let source_id = SourceId::for_registry(&url).unwrap();
881+
let formatted = format!("{}", source_id.as_url());
882+
let deserialized = SourceId::from_url(&formatted).unwrap();
883+
assert_eq!(formatted, "sparse+https://my-crates.io/");
884+
assert_eq!(source_id, deserialized);
885+
}
867886
}

src/cargo/util/canonical_url.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::util::{errors::CargoResult, IntoUrl};
1+
use crate::util::errors::CargoResult;
22
use std::hash::{self, Hash};
33
use url::Url;
44

@@ -56,17 +56,6 @@ impl CanonicalUrl {
5656
url.path_segments_mut().unwrap().pop().push(&last);
5757
}
5858

59-
// Ignore the protocol specifier (if any).
60-
if url.scheme().starts_with("sparse+") {
61-
// NOTE: it is illegal to use set_scheme to change sparse+http(s) to http(s).
62-
url = url
63-
.to_string()
64-
.strip_prefix("sparse+")
65-
.expect("we just found that prefix")
66-
.into_url()
67-
.expect("a valid url without a protocol specifier should still be valid");
68-
}
69-
7059
Ok(CanonicalUrl(url))
7160
}
7261

tests/testsuite/install.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2142,3 +2142,67 @@ fn failed_install_retains_temp_directory() {
21422142
assert!(path.exists());
21432143
assert!(path.join("release/deps").exists());
21442144
}
2145+
2146+
#[cargo_test]
2147+
fn sparse_install() {
2148+
// Checks for an issue where uninstalling something corrupted
2149+
// the SourceIds of sparse registries.
2150+
// See https://github.com/rust-lang/cargo/issues/11751
2151+
let _registry = registry::RegistryBuilder::new().http_index().build();
2152+
2153+
pkg("foo", "0.0.1");
2154+
pkg("bar", "0.0.1");
2155+
2156+
cargo_process("install foo --registry dummy-registry")
2157+
.with_stderr(
2158+
"\
2159+
[UPDATING] `dummy-registry` index
2160+
[DOWNLOADING] crates ...
2161+
[DOWNLOADED] foo v0.0.1 (registry `dummy-registry`)
2162+
[INSTALLING] foo v0.0.1 (registry `dummy-registry`)
2163+
[UPDATING] `dummy-registry` index
2164+
[COMPILING] foo v0.0.1 (registry `dummy-registry`)
2165+
[FINISHED] release [optimized] target(s) in [..]
2166+
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
2167+
[INSTALLED] package `foo v0.0.1 (registry `dummy-registry`)` (executable `foo[EXE]`)
2168+
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
2169+
",
2170+
)
2171+
.run();
2172+
assert_has_installed_exe(cargo_home(), "foo");
2173+
let assert_v1 = |expected| {
2174+
let v1 = fs::read_to_string(paths::home().join(".cargo/.crates.toml")).unwrap();
2175+
compare::assert_match_exact(expected, &v1);
2176+
};
2177+
assert_v1(
2178+
r#"[v1]
2179+
"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
2180+
"#,
2181+
);
2182+
cargo_process("install bar").run();
2183+
assert_has_installed_exe(cargo_home(), "bar");
2184+
assert_v1(
2185+
r#"[v1]
2186+
"bar 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = ["bar[EXE]"]
2187+
"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
2188+
"#,
2189+
);
2190+
2191+
cargo_process("uninstall bar")
2192+
.with_stderr("[REMOVING] [CWD]/home/.cargo/bin/bar[EXE]")
2193+
.run();
2194+
assert_has_not_installed_exe(cargo_home(), "bar");
2195+
assert_v1(
2196+
r#"[v1]
2197+
"foo 0.0.1 (sparse+http://127.0.0.1:[..]/index/)" = ["foo[EXE]"]
2198+
"#,
2199+
);
2200+
cargo_process("uninstall foo")
2201+
.with_stderr("[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]")
2202+
.run();
2203+
assert_has_not_installed_exe(cargo_home(), "foo");
2204+
assert_v1(
2205+
r#"[v1]
2206+
"#,
2207+
);
2208+
}

0 commit comments

Comments
 (0)