Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 0 additions & 54 deletions crates/uv-distribution-types/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,26 +169,6 @@ impl UrlString {
.map(|(path, _)| Cow::Owned(UrlString(SmallString::from(path))))
.unwrap_or(Cow::Borrowed(self))
}

/// Return the [`UrlString`] (as a [`Cow`]) with trailing slash removed.
///
/// This matches the semantics of [`Url::pop_if_empty`], which will not trim a trailing slash if
/// it's the only path segment, e.g., `https://example.com/` would be unchanged.
#[must_use]
pub fn without_trailing_slash(&self) -> Cow<'_, Self> {
self.as_ref()
.strip_suffix('/')
.filter(|path| {
// Only strip the trailing slash if there's _another_ trailing slash that isn't a
// part of the scheme.
path.split_once("://")
.map(|(_scheme, rest)| rest)
.unwrap_or(path)
.contains('/')
})
.map(|path| Cow::Owned(UrlString(SmallString::from(path))))
.unwrap_or(Cow::Borrowed(self))
}
}

impl AsRef<str> for UrlString {
Expand Down Expand Up @@ -283,38 +263,4 @@ mod tests {
);
assert!(matches!(url.without_fragment(), Cow::Owned(_)));
}

#[test]
fn without_trailing_slash() {
// Borrows a URL without a slash
let url = UrlString("https://example.com/path".into());
assert_eq!(&*url.without_trailing_slash(), &url);
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));

// Removes the trailing slash if present on the URL
let url = UrlString("https://example.com/path/".into());
assert_eq!(
&*url.without_trailing_slash(),
&UrlString("https://example.com/path".into())
);
assert!(matches!(url.without_trailing_slash(), Cow::Owned(_)));

// Does not remove a trailing slash if it's the only path segment
let url = UrlString("https://example.com/".into());
assert_eq!(&*url.without_trailing_slash(), &url);
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));

// Does not remove a trailing slash if it's the only path segment with a missing scheme
let url = UrlString("example.com/".into());
assert_eq!(&*url.without_trailing_slash(), &url);
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));

// Removes the trailing slash when the scheme is missing
let url = UrlString("example.com/path/".into());
assert_eq!(
&*url.without_trailing_slash(),
&UrlString("example.com/path".into())
);
assert!(matches!(url.without_trailing_slash(), Cow::Owned(_)));
}
}
17 changes: 4 additions & 13 deletions crates/uv-distribution-types/src/index_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ impl IndexUrl {
///
/// If no root directory is provided, relative paths are resolved against the current working
/// directory.
///
/// Normalizes non-file URLs by removing trailing slashes for consistency.
pub fn parse(path: &str, root_dir: Option<&Path>) -> Result<Self, IndexUrlError> {
let url = match split_scheme(path) {
Some((scheme, ..)) => {
Expand Down Expand Up @@ -258,20 +256,13 @@ impl<'de> serde::de::Deserialize<'de> for IndexUrl {
}

impl From<VerbatimUrl> for IndexUrl {
fn from(mut url: VerbatimUrl) -> Self {
fn from(url: VerbatimUrl) -> Self {
if url.scheme() == "file" {
Self::Path(Arc::new(url))
} else if *url.raw() == *PYPI_URL {
Self::Pypi(Arc::new(url))
} else {
// Remove trailing slashes for consistency. They'll be re-added if necessary when
// querying the Simple API.
if let Ok(mut path_segments) = url.raw_mut().path_segments_mut() {
path_segments.pop_if_empty();
}
if *url.raw() == *PYPI_URL {
Self::Pypi(Arc::new(url))
} else {
Self::Url(Arc::new(url))
}
Self::Url(Arc::new(url))
}
}
}
Expand Down
5 changes: 0 additions & 5 deletions crates/uv-pep508/src/verbatim_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,6 @@ impl VerbatimUrl {
&self.url
}

/// Return a mutable reference to the underlying [`DisplaySafeUrl`].
pub fn raw_mut(&mut self) -> &mut DisplaySafeUrl {
&mut self.url
}

/// Convert a [`VerbatimUrl`] into a [`DisplaySafeUrl`].
pub fn to_url(&self) -> DisplaySafeUrl {
self.url.clone()
Expand Down
12 changes: 3 additions & 9 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1478,23 +1478,17 @@ impl Lock {
if let Source::Registry(index) = &package.id.source {
match index {
RegistrySource::Url(url) => {
// Normalize URL before validating.
let url = url.without_trailing_slash();
if remotes
.as_ref()
.is_some_and(|remotes| !remotes.contains(&url))
.is_some_and(|remotes| !remotes.contains(url))
{
let name = &package.id.name;
let version = &package
.id
.version
.as_ref()
.expect("version for registry source");
return Ok(SatisfiesResult::MissingRemoteIndex(
name,
version,
url.into_owned(),
));
return Ok(SatisfiesResult::MissingRemoteIndex(name, version, url));
}
}
RegistrySource::Path(path) => {
Expand Down Expand Up @@ -1799,7 +1793,7 @@ pub enum SatisfiesResult<'lock> {
/// The lockfile is missing a workspace member.
MissingRoot(PackageName),
/// The lockfile referenced a remote index that was not provided
MissingRemoteIndex(&'lock PackageName, &'lock Version, UrlString),
MissingRemoteIndex(&'lock PackageName, &'lock Version, &'lock UrlString),
/// The lockfile referenced a local index that was not provided
MissingLocalIndex(&'lock PackageName, &'lock Version, &'lock Path),
/// A package in the lockfile contains different `requires-dist` metadata than expected.
Expand Down
24 changes: 12 additions & 12 deletions crates/uv/tests/it/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4374,7 +4374,7 @@ fn add_lower_bound_local() -> Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r#"
pyproject_toml, @r###"
[project]
name = "project"
version = "0.1.0"
Expand All @@ -4384,8 +4384,8 @@ fn add_lower_bound_local() -> Result<()> {
]

[[tool.uv.index]]
url = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html"
"#
url = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/"
"###
);
});

Expand All @@ -4403,7 +4403,7 @@ fn add_lower_bound_local() -> Result<()> {
[[package]]
name = "local-simple-a"
version = "1.2.3+foo"
source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html" }
source = { registry = "https://astral-sh.github.io/packse/PACKSE_VERSION/simple-html/" }
sdist = { url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/local_simple_a-1.2.3+foo.tar.gz", hash = "sha256:ebd55c4a79d0a5759126657cb289ff97558902abcfb142e036b993781497edac" }
wheels = [
{ url = "https://astral-sh.github.io/packse/PACKSE_VERSION/files/local_simple_a-1.2.3+foo-py3-none-any.whl", hash = "sha256:6f30e2e709b3e171cd734bb58705229a582587c29e0a7041227435583c7224cc" },
Expand Down Expand Up @@ -9259,7 +9259,7 @@ fn add_index_with_trailing_slash() -> Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r#"
pyproject_toml, @r###"
[project]
name = "project"
version = "0.1.0"
Expand All @@ -9272,8 +9272,8 @@ fn add_index_with_trailing_slash() -> Result<()> {
constraint-dependencies = ["markupsafe<3"]

[[tool.uv.index]]
url = "https://pypi.org/simple"
"#
url = "https://pypi.org/simple/"
"###
);
});

Expand All @@ -9297,7 +9297,7 @@ fn add_index_with_trailing_slash() -> Result<()> {
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
source = { registry = "https://pypi.org/simple/" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" },
Expand Down Expand Up @@ -11194,7 +11194,7 @@ fn repeated_index_cli_reversed() -> Result<()> {
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r#"
pyproject_toml, @r###"
[project]
name = "project"
version = "0.1.0"
Expand All @@ -11204,8 +11204,8 @@ fn repeated_index_cli_reversed() -> Result<()> {
]

[[tool.uv.index]]
url = "https://test.pypi.org/simple"
"#
url = "https://test.pypi.org/simple/"
"###
);
});

Expand All @@ -11226,7 +11226,7 @@ fn repeated_index_cli_reversed() -> Result<()> {
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://test.pypi.org/simple" }
source = { registry = "https://test.pypi.org/simple/" }
sdist = { url = "https://test-files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:16.826Z" }
wheels = [
{ url = "https://test-files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:14.843Z" },
Expand Down
Loading
Loading