From 6afbb02798542dcbced0c148cd6235365f4665fb Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 22 Feb 2024 11:44:36 -0500 Subject: [PATCH] Allow duplicate URLs that resolve to the same canonical URL (#1877) Closes https://github.com/astral-sh/uv/issues/1865. Closes https://github.com/astral-sh/uv/issues/1866. --- crates/uv-resolver/src/resolver/urls.rs | 42 ++++++++++++++++--------- crates/uv/tests/pip_compile.rs | 24 ++++++++++++++ 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index 208aeb98f..d206d6fe6 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -28,11 +28,15 @@ impl Urls { if let Some(pep508_rs::VersionOrUrl::Url(url)) = &requirement.version_or_url { if let Some(previous) = urls.insert(requirement.name.clone(), url.clone()) { - return Err(ResolveError::ConflictingUrlsDirect( - requirement.name.clone(), - previous.verbatim().to_string(), - url.verbatim().to_string(), - )); + if cache_key::CanonicalUrl::new(previous.raw()) + != cache_key::CanonicalUrl::new(url.raw()) + { + return Err(ResolveError::ConflictingUrlsDirect( + requirement.name.clone(), + previous.verbatim().to_string(), + url.verbatim().to_string(), + )); + } } } } @@ -42,21 +46,29 @@ impl Urls { if let Some(previous) = urls.insert(metadata.name.clone(), editable_requirement.url.clone()) { - return Err(ResolveError::ConflictingUrlsDirect( - metadata.name.clone(), - previous.verbatim().to_string(), - editable_requirement.url.verbatim().to_string(), - )); + if cache_key::CanonicalUrl::new(previous.raw()) + != cache_key::CanonicalUrl::new(editable_requirement.raw()) + { + return Err(ResolveError::ConflictingUrlsDirect( + metadata.name.clone(), + previous.verbatim().to_string(), + editable_requirement.url.verbatim().to_string(), + )); + } } for req in &metadata.requires_dist { if let Some(pep508_rs::VersionOrUrl::Url(url)) = &req.version_or_url { if let Some(previous) = urls.insert(req.name.clone(), url.clone()) { - return Err(ResolveError::ConflictingUrlsDirect( - req.name.clone(), - previous.verbatim().to_string(), - url.verbatim().to_string(), - )); + if cache_key::CanonicalUrl::new(previous.raw()) + != cache_key::CanonicalUrl::new(url.raw()) + { + return Err(ResolveError::ConflictingUrlsDirect( + req.name.clone(), + previous.verbatim().to_string(), + url.verbatim().to_string(), + )); + } } } } diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 89e872f4b..2857465e0 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -1363,6 +1363,30 @@ fn conflicting_transitive_url_dependency() -> Result<()> { Ok(()) } +/// Request Werkzeug via two different URLs which resolve to the same canonical version. +#[test] +fn compatible_repeated_url_dependency() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@2.0.0\nwerkzeug @ git+https://github.com/pallets/werkzeug@2.0.0")?; + + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z requirements.in + werkzeug @ git+https://github.com/pallets/werkzeug@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + Ok(()) +} + /// Request `transitive_url_dependency`, which depends on `git+https://github.com/pallets/werkzeug@2.0.0`. /// Since this URL isn't declared upfront, we should reject it. #[test]