From 6dfac4559b9c5bfd0a8fbf1787ce25a0d91abecd Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 8 Feb 2025 09:23:31 -0600 Subject: [PATCH] Fix credential caching for index roots when URL ends in `simple/` (#11336) Closes https://github.com/astral-sh/uv/issues/11244 See test failure at https://github.com/astral-sh/uv/pull/11336/commits/e12f98a3e40ddb61edd2fd32be4bfcf01c160102 --- crates/uv-distribution-types/src/index.rs | 11 ++++-- crates/uv/tests/it/lock.rs | 46 +++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index e8465ddae..d949c3776 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -160,14 +160,19 @@ impl Index { /// For indexes with a `/simple` endpoint, this is simply the URL with the final segment /// removed. This is useful, e.g., for credential propagation to other endpoints on the index. pub fn root_url(&self) -> Option { - let segments = self.raw_url().path_segments()?; - let last = segments.last()?; + let mut segments = self.raw_url().path_segments()?; + let last = match segments.next_back()? { + // If the last segment is empty due to a trailing `/`, skip it (as in `pop_if_empty`) + "" => segments.next_back()?, + segment => segment, + }; + if !last.eq_ignore_ascii_case("simple") { return None; } let mut url = self.raw_url().clone(); - url.path_segments_mut().ok()?.pop(); + url.path_segments_mut().ok()?.pop_if_empty().pop(); Some(url) } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 97e482873..d7f940fd9 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -8358,6 +8358,52 @@ fn lock_multiple_indexes_same_realm_different_credentials() -> Result<()> { Ok(()) } +// Same as [`lock_multiple_indexes_same_realm_different_credentials`], but with trailing slashes +// on the index URL +#[test] +fn lock_multiple_indexes_same_realm_different_credentials_trailing_slash() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "foo" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig", "anyio"] + + [tool.uv.sources] + iniconfig = { index = "internal-proxy-heron" } + anyio = { index = "internal-proxy-eagle" } + + [[tool.uv.index]] + name = "internal-proxy-heron" + url = "https://pypi-proxy.fly.dev/basic-auth-heron/simple/" + + [[tool.uv.index]] + name = "internal-proxy-eagle" + url = "https://pypi-proxy.fly.dev/basic-auth-eagle/simple/" + "#, + )?; + + // Provide credentials via environment variables. + uv_snapshot!(context.filters(), context.lock() + .env(EnvVars::index_username("INTERNAL_PROXY_HERON"), "public") + .env(EnvVars::index_password("INTERNAL_PROXY_HERON"), "heron") + .env(EnvVars::index_username("INTERNAL_PROXY_EAGLE"), "public") + .env(EnvVars::index_password("INTERNAL_PROXY_EAGLE"), "eagle"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "###); + + Ok(()) +} + /// Resolve against an index that uses relative links. #[test] fn lock_relative_index() -> Result<()> {