mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Add DisplaySafeUrl
newtype to prevent leaking of credentials by default (#13560)
Prior to this PR, there were numerous places where uv would leak credentials in logs. We had a way to mask credentials by calling methods or a recently-added `redact_url` function, but this was not secure by default. There were a number of other types (like `GitUrl`) that would leak credentials on display. This PR adds a `DisplaySafeUrl` newtype to prevent leaking credentials when logging by default. It takes a maximalist approach, replacing the use of `Url` almost everywhere. This includes when first parsing config files, when storing URLs in types like `GitUrl`, and also when storing URLs in types that in practice will never contain credentials (like `DirectorySourceUrl`). The idea is to make it easy for developers to do the right thing and for the compiler to support this (and to minimize ever having to manually convert back and forth). Displaying credentials now requires an active step. Note that despite this maximalist approach, the use of the newtype should be zero cost. One conspicuous place this PR does not use `DisplaySafeUrl` is in the `uv-auth` crate. That would require new clones since there are calls to `request.url()` that return a `&Url`. One option would have been to make `DisplaySafeUrl` wrap a `Cow`, but this would lead to lifetime annotations all over the codebase. I've created a separate PR based on this one (#13576) that updates `uv-auth` to use `DisplaySafeUrl` with one new clone. We can discuss the tradeoffs there. Most of this PR just replaces `Url` with `DisplaySafeUrl`. The core is `uv_redacted/lib.rs`, where the newtype is implemented. To make it easier to review the rest, here are some points of note: * `DisplaySafeUrl` has a `Display` implementation that masks credentials. Currently, it will still display the username when there is both a username and password. If we think is the wrong choice, it can now be changed in one place. * `DisplaySafeUrl` has a `remove_credentials()` method and also a `.to_string_with_credentials()` method. This allows us to use it in a variety of scenarios. * `IndexUrl::redacted()` was renamed to `IndexUrl::removed_credentials()` to make it clearer that we are not masking. * We convert from a `DisplaySafeUrl` to a `Url` when calling `reqwest` methods like `.get()` and `.head()`. * We convert from a `DisplaySafeUrl` to a `Url` when creating a `uv_auth::Index`. That is because, as mentioned above, I will be updating the `uv_auth` crate to use this newtype in a separate PR. * A number of tests (e.g., in `pip_install.rs`) that formerly used filters to mask tokens in the test output no longer need those filters since tokens in URLs are now masked automatically. * The one place we are still knowingly writing credentials to `pyproject.toml` is when a URL with credentials is passed to `uv add` with `--raw`. Since displaying credentials is no longer automatic, I have added a `to_string_with_credentials()` method to the `Pep508Url` trait. This is used when `--raw` is passed. Adding it to that trait is a bit weird, but it's the simplest way to achieve the goal. I'm open to suggestions on how to improve this, but note that because of the way we're using generic bounds, it's not as simple as just creating a separate trait for that method.
This commit is contained in:
parent
b80cafd5e8
commit
c19a294a48
100 changed files with 1266 additions and 2249 deletions
|
@ -33,6 +33,7 @@ uv-pep508 = { workspace = true }
|
|||
uv-platform-tags = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-python = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-requirements-txt = { workspace = true }
|
||||
uv-small-str = { workspace = true }
|
||||
uv-static = { workspace = true }
|
||||
|
|
|
@ -33,6 +33,7 @@ use uv_pep440::Version;
|
|||
use uv_pep508::{MarkerEnvironment, MarkerTree, VerbatimUrl};
|
||||
use uv_platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||
use uv_pypi_types::{HashDigests, Hashes, ParsedGitUrl, VcsKind};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
|
||||
use crate::lock::export::ExportableRequirements;
|
||||
|
@ -93,7 +94,7 @@ pub enum PylockTomlErrorKind {
|
|||
#[error("`packages.vcs` entry for `{0}` must have a `url` or `path`")]
|
||||
VcsMissingPathUrl(PackageName),
|
||||
#[error("URL must end in a valid wheel filename: `{0}`")]
|
||||
UrlMissingFilename(Url),
|
||||
UrlMissingFilename(DisplaySafeUrl),
|
||||
#[error("Path must end in a valid wheel filename: `{0}`")]
|
||||
PathMissingFilename(Box<Path>),
|
||||
#[error("Failed to convert path to URL")]
|
||||
|
@ -204,7 +205,7 @@ pub struct PylockTomlPackage {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<Version>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub index: Option<Url>,
|
||||
pub index: Option<DisplaySafeUrl>,
|
||||
#[serde(
|
||||
skip_serializing_if = "uv_pep508::marker::ser::is_empty",
|
||||
serialize_with = "uv_pep508::marker::ser::serialize",
|
||||
|
@ -247,7 +248,7 @@ struct PylockTomlDirectory {
|
|||
struct PylockTomlVcs {
|
||||
r#type: VcsKind,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -261,7 +262,7 @@ struct PylockTomlVcs {
|
|||
#[serde(rename_all = "kebab-case")]
|
||||
struct PylockTomlArchive {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -284,7 +285,7 @@ struct PylockTomlSdist {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
name: Option<SmallString>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(
|
||||
|
@ -305,7 +306,7 @@ struct PylockTomlWheel {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
name: Option<WheelFilename>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<Url>,
|
||||
url: Option<DisplaySafeUrl>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<PortablePathBuf>,
|
||||
#[serde(
|
||||
|
@ -1324,7 +1325,7 @@ impl PylockTomlWheel {
|
|||
&self,
|
||||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
index: Option<&Url>,
|
||||
index: Option<&DisplaySafeUrl>,
|
||||
) -> Result<RegistryBuiltWheel, PylockTomlErrorKind> {
|
||||
let filename = self.filename(name)?.into_owned();
|
||||
|
||||
|
@ -1332,7 +1333,8 @@ impl PylockTomlWheel {
|
|||
UrlString::from(url)
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let path = install_path.join(path);
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
let url = DisplaySafeUrl::from_file_path(path)
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
UrlString::from(url)
|
||||
} else {
|
||||
return Err(PylockTomlErrorKind::WheelMissingPathUrl(name.clone()));
|
||||
|
@ -1408,8 +1410,10 @@ impl PylockTomlVcs {
|
|||
let mut url = if let Some(url) = self.url.as_ref() {
|
||||
url.clone()
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
Url::from_directory_path(install_path.join(path))
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?
|
||||
DisplaySafeUrl::from(
|
||||
Url::from_directory_path(install_path.join(path))
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?,
|
||||
)
|
||||
} else {
|
||||
return Err(PylockTomlErrorKind::VcsMissingPathUrl(name.clone()));
|
||||
};
|
||||
|
@ -1427,7 +1431,7 @@ impl PylockTomlVcs {
|
|||
};
|
||||
|
||||
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git_url.clone(),
|
||||
subdirectory: subdirectory.clone(),
|
||||
});
|
||||
|
@ -1469,7 +1473,7 @@ impl PylockTomlSdist {
|
|||
install_path: &Path,
|
||||
name: &PackageName,
|
||||
version: Option<&Version>,
|
||||
index: Option<&Url>,
|
||||
index: Option<&DisplaySafeUrl>,
|
||||
) -> Result<RegistrySourceDist, PylockTomlErrorKind> {
|
||||
let filename = self.filename(name)?.into_owned();
|
||||
let ext = SourceDistExtension::from_path(filename.as_ref())?;
|
||||
|
@ -1485,7 +1489,8 @@ impl PylockTomlSdist {
|
|||
UrlString::from(url)
|
||||
} else if let Some(path) = self.path.as_ref() {
|
||||
let path = install_path.join(path);
|
||||
let url = Url::from_file_path(path).map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
let url = DisplaySafeUrl::from_file_path(path)
|
||||
.map_err(|()| PylockTomlErrorKind::PathToUrl)?;
|
||||
UrlString::from(url)
|
||||
} else {
|
||||
return Err(PylockTomlErrorKind::SdistMissingPathUrl(name.clone()));
|
||||
|
|
|
@ -13,6 +13,7 @@ use uv_fs::Simplified;
|
|||
use uv_git_types::GitReference;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::lock::export::{ExportableRequirement, ExportableRequirements};
|
||||
use crate::lock::{Package, PackageId, Source};
|
||||
|
@ -94,7 +95,7 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
|
|||
.expect("Internal Git URLs must have supported schemes");
|
||||
|
||||
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git_url.clone(),
|
||||
subdirectory: git.subdirectory.clone(),
|
||||
});
|
||||
|
@ -102,7 +103,7 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
|
|||
write!(f, "{} @ {}", package.id.name, url)?;
|
||||
}
|
||||
Source::Direct(url, direct) => {
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: url.to_url().map_err(|_| std::fmt::Error)?,
|
||||
subdirectory: direct.subdirectory.clone(),
|
||||
ext: DistExtension::Source(SourceDistExtension::TarGz),
|
||||
|
|
|
@ -30,7 +30,7 @@ use uv_distribution_types::{
|
|||
Dist, DistributionMetadata, FileLocation, GitSourceDist, IndexLocations, IndexMetadata,
|
||||
IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel,
|
||||
RegistrySourceDist, RemoteSource, Requirement, RequirementSource, ResolvedDist, StaticMetadata,
|
||||
ToUrlError, UrlString, redact_credentials,
|
||||
ToUrlError, UrlString,
|
||||
};
|
||||
use uv_fs::{PortablePath, PortablePathBuf, relative_to};
|
||||
use uv_git::{RepositoryReference, ResolvedRepositoryReference};
|
||||
|
@ -45,6 +45,7 @@ use uv_pypi_types::{
|
|||
ConflictPackage, Conflicts, HashAlgorithm, HashDigest, HashDigests, Hashes, ParsedArchiveUrl,
|
||||
ParsedGitUrl,
|
||||
};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_small_str::SmallString;
|
||||
use uv_types::{BuildContext, HashStrategy};
|
||||
use uv_workspace::WorkspaceMember;
|
||||
|
@ -1404,7 +1405,7 @@ impl Lock {
|
|||
.into_iter()
|
||||
.filter_map(|index| match index.url() {
|
||||
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||
Some(UrlString::from(index.url().redacted().as_ref()))
|
||||
Some(UrlString::from(index.url().without_credentials().as_ref()))
|
||||
}
|
||||
IndexUrl::Path(_) => None,
|
||||
})
|
||||
|
@ -2238,7 +2239,7 @@ impl Package {
|
|||
Source::Direct(url, direct) => {
|
||||
let filename: WheelFilename =
|
||||
self.wheels[best_wheel_index].filename.clone();
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: url.to_url().map_err(LockErrorKind::InvalidUrl)?,
|
||||
subdirectory: direct.subdirectory.clone(),
|
||||
ext: DistExtension::Wheel,
|
||||
|
@ -2400,7 +2401,7 @@ impl Package {
|
|||
GitUrl::from_commit(url, GitReference::from(git.kind.clone()), git.precise)?;
|
||||
|
||||
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git_url.clone(),
|
||||
subdirectory: git.subdirectory.clone(),
|
||||
});
|
||||
|
@ -2419,7 +2420,7 @@ impl Package {
|
|||
return Ok(None);
|
||||
};
|
||||
let location = url.to_url().map_err(LockErrorKind::InvalidUrl)?;
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: location.clone(),
|
||||
subdirectory: direct.subdirectory.clone(),
|
||||
ext: DistExtension::Source(ext),
|
||||
|
@ -2498,8 +2499,9 @@ impl Package {
|
|||
name: name.clone(),
|
||||
version: version.clone(),
|
||||
})?;
|
||||
let file_url = Url::from_file_path(workspace_root.join(path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let file_url =
|
||||
DisplaySafeUrl::from_file_path(workspace_root.join(path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let filename = sdist
|
||||
.filename()
|
||||
.ok_or_else(|| LockErrorKind::MissingFilename {
|
||||
|
@ -3192,7 +3194,7 @@ impl Source {
|
|||
match index_url {
|
||||
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
|
||||
// Remove any sensitive credentials from the index URL.
|
||||
let redacted = index_url.redacted();
|
||||
let redacted = index_url.without_credentials();
|
||||
let source = RegistrySource::Url(UrlString::from(redacted.as_ref()));
|
||||
Ok(Source::Registry(source))
|
||||
}
|
||||
|
@ -3405,7 +3407,7 @@ impl TryFrom<SourceWire> for Source {
|
|||
match wire {
|
||||
Registry { registry } => Ok(Source::Registry(registry.into())),
|
||||
Git { git } => {
|
||||
let url = Url::parse(&git)
|
||||
let url = DisplaySafeUrl::parse(&git)
|
||||
.map_err(|err| SourceParseError::InvalidUrl {
|
||||
given: git.to_string(),
|
||||
err,
|
||||
|
@ -3913,12 +3915,12 @@ impl From<GitSourceKind> for GitReference {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct the lockfile-compatible [`URL`] for a [`GitSourceDist`].
|
||||
fn locked_git_url(git_dist: &GitSourceDist) -> Url {
|
||||
/// Construct the lockfile-compatible [`DisplaySafeUrl`] for a [`GitSourceDist`].
|
||||
fn locked_git_url(git_dist: &GitSourceDist) -> DisplaySafeUrl {
|
||||
let mut url = git_dist.git.repository().clone();
|
||||
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut url);
|
||||
// Remove the credentials.
|
||||
url.remove_credentials();
|
||||
|
||||
// Clear out any existing state.
|
||||
url.set_fragment(None);
|
||||
|
@ -4183,8 +4185,9 @@ impl Wheel {
|
|||
.into());
|
||||
}
|
||||
};
|
||||
let file_url = Url::from_file_path(root.join(index_path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let file_url =
|
||||
DisplaySafeUrl::from_file_path(root.join(index_path).join(file_path))
|
||||
.map_err(|()| LockErrorKind::PathToUrl)?;
|
||||
let file = Box::new(uv_distribution_types::File {
|
||||
dist_info_metadata: false,
|
||||
filename: SmallString::from(filename.to_string()),
|
||||
|
@ -4571,8 +4574,8 @@ fn normalize_file_location(location: &FileLocation) -> Result<UrlString, ToUrlEr
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert a [`Url`] into a normalized [`UrlString`] by removing the fragment.
|
||||
fn normalize_url(mut url: Url) -> UrlString {
|
||||
/// Convert a [`DisplaySafeUrl`] into a normalized [`UrlString`] by removing the fragment.
|
||||
fn normalize_url(mut url: DisplaySafeUrl) -> UrlString {
|
||||
url.set_fragment(None);
|
||||
UrlString::from(url)
|
||||
}
|
||||
|
@ -4606,8 +4609,8 @@ fn normalize_requirement(
|
|||
let git = {
|
||||
let mut repository = git.repository().clone();
|
||||
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut repository);
|
||||
// Remove the credentials.
|
||||
repository.remove_credentials();
|
||||
|
||||
// Remove the fragment and query from the URL; they're already present in the source.
|
||||
repository.set_fragment(None);
|
||||
|
@ -4617,7 +4620,7 @@ fn normalize_requirement(
|
|||
};
|
||||
|
||||
// Reconstruct the PEP 508 URL from the underlying data.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedGitUrl {
|
||||
url: git.clone(),
|
||||
subdirectory: subdirectory.clone(),
|
||||
});
|
||||
|
@ -4692,7 +4695,7 @@ fn normalize_requirement(
|
|||
let index = index
|
||||
.map(|index| index.url.into_url())
|
||||
.map(|mut index| {
|
||||
redact_credentials(&mut index);
|
||||
index.remove_credentials();
|
||||
index
|
||||
})
|
||||
.map(|index| IndexMetadata::from(IndexUrl::from(VerbatimUrl::from_url(index))));
|
||||
|
@ -4715,14 +4718,14 @@ fn normalize_requirement(
|
|||
ext,
|
||||
url: _,
|
||||
} => {
|
||||
// Redact the credentials.
|
||||
redact_credentials(&mut location);
|
||||
// Remove the credentials.
|
||||
location.remove_credentials();
|
||||
|
||||
// Remove the fragment from the URL; it's already present in the source.
|
||||
location.set_fragment(None);
|
||||
|
||||
// Reconstruct the PEP 508 URL from the underlying data.
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
let url = DisplaySafeUrl::from(ParsedArchiveUrl {
|
||||
url: location.clone(),
|
||||
subdirectory: subdirectory.clone(),
|
||||
ext,
|
||||
|
|
|
@ -1518,7 +1518,7 @@ impl std::fmt::Display for PubGrubHint {
|
|||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
name.cyan(),
|
||||
found_index.redacted().cyan(),
|
||||
found_index.without_credentials().cyan(),
|
||||
PackageRange::compatibility(&PubGrubPackage::base(name), range, None).cyan(),
|
||||
next_index.cyan(),
|
||||
"--index-strategy unsafe-best-match".green(),
|
||||
|
@ -1530,7 +1530,7 @@ impl std::fmt::Display for PubGrubHint {
|
|||
"{}{} An index URL ({}) could not be queried due to a lack of valid authentication credentials ({}).",
|
||||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
index.redacted().cyan(),
|
||||
index.without_credentials().cyan(),
|
||||
"401 Unauthorized".red(),
|
||||
)
|
||||
}
|
||||
|
@ -1540,7 +1540,7 @@ impl std::fmt::Display for PubGrubHint {
|
|||
"{}{} An index URL ({}) could not be queried due to a lack of valid authentication credentials ({}).",
|
||||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
index.redacted().cyan(),
|
||||
index.without_credentials().cyan(),
|
||||
"403 Forbidden".red(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use url::Url;
|
||||
|
||||
use uv_git::GitResolver;
|
||||
use uv_pep508::VerbatimUrl;
|
||||
use uv_pypi_types::{ParsedGitUrl, ParsedUrl, VerbatimParsedUrl};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
/// Map a URL to a precise URL, if possible.
|
||||
pub(crate) fn url_to_precise(url: VerbatimParsedUrl, git: &GitResolver) -> VerbatimParsedUrl {
|
||||
|
@ -26,7 +25,7 @@ pub(crate) fn url_to_precise(url: VerbatimParsedUrl, git: &GitResolver) -> Verba
|
|||
url: new_git_url,
|
||||
subdirectory: subdirectory.clone(),
|
||||
};
|
||||
let new_url = Url::from(new_parsed_url.clone());
|
||||
let new_url = DisplaySafeUrl::from(new_parsed_url.clone());
|
||||
let new_verbatim_url = apply_redirect(&url.verbatim, new_url);
|
||||
VerbatimParsedUrl {
|
||||
parsed_url: ParsedUrl::Git(new_parsed_url),
|
||||
|
@ -36,7 +35,7 @@ pub(crate) fn url_to_precise(url: VerbatimParsedUrl, git: &GitResolver) -> Verba
|
|||
|
||||
/// Given a [`VerbatimUrl`] and a redirect, apply the redirect to the URL while preserving as much
|
||||
/// of the verbatim representation as possible.
|
||||
fn apply_redirect(url: &VerbatimUrl, redirect: Url) -> VerbatimUrl {
|
||||
fn apply_redirect(url: &VerbatimUrl, redirect: DisplaySafeUrl) -> VerbatimUrl {
|
||||
let redirect = VerbatimUrl::from_url(redirect);
|
||||
|
||||
// The redirect should be the "same" URL, but with a specific commit hash added after the `@`.
|
||||
|
@ -85,9 +84,8 @@ fn apply_redirect(url: &VerbatimUrl, redirect: Url) -> VerbatimUrl {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use url::Url;
|
||||
|
||||
use uv_pep508::VerbatimUrl;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::redirect::apply_redirect;
|
||||
|
||||
|
@ -97,8 +95,9 @@ mod tests {
|
|||
// to the given representation.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git")?
|
||||
.with_given("git+https://github.com/flask.git");
|
||||
let redirect =
|
||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
)?;
|
||||
|
||||
let expected = VerbatimUrl::parse_url(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
|
@ -111,8 +110,9 @@ mod tests {
|
|||
// representation.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")?
|
||||
.with_given("git+https://${DOMAIN}.com/flask.git@main");
|
||||
let redirect =
|
||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
)?;
|
||||
|
||||
let expected = VerbatimUrl::parse_url(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
|
@ -123,8 +123,9 @@ mod tests {
|
|||
// If there's a conflict after the `@`, discard the original representation.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")?
|
||||
.with_given("git+https://github.com/flask.git@${TAG}");
|
||||
let redirect =
|
||||
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
)?;
|
||||
|
||||
let expected = VerbatimUrl::parse_url(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
|
||||
|
@ -134,7 +135,7 @@ mod tests {
|
|||
// We should preserve subdirectory fragments.
|
||||
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git#subdirectory=src")?
|
||||
.with_given("git+https://github.com/flask.git#subdirectory=src");
|
||||
let redirect = Url::parse(
|
||||
let redirect = DisplaySafeUrl::parse(
|
||||
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src",
|
||||
)?;
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
|||
// `# from https://pypi.org/simple`).
|
||||
if self.include_index_annotation {
|
||||
if let Some(index) = node.dist.index() {
|
||||
let url = index.redacted();
|
||||
let url = index.without_credentials();
|
||||
writeln!(f, "{}", format!(" # from {url}").green())?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_distribution_types::{BuildableSource, VersionOrUrlRef};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
pub type BuildId = usize;
|
||||
|
||||
|
@ -31,10 +30,10 @@ pub trait Reporter: Send + Sync {
|
|||
fn on_download_complete(&self, name: &PackageName, id: usize);
|
||||
|
||||
/// Callback to invoke when a repository checkout begins.
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize;
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize;
|
||||
|
||||
/// Callback to invoke when a repository checkout completes.
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize);
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize);
|
||||
}
|
||||
|
||||
impl dyn Reporter {
|
||||
|
@ -62,11 +61,11 @@ impl uv_distribution::Reporter for Facade {
|
|||
self.reporter.on_build_complete(source, id);
|
||||
}
|
||||
|
||||
fn on_checkout_start(&self, url: &Url, rev: &str) -> usize {
|
||||
fn on_checkout_start(&self, url: &DisplaySafeUrl, rev: &str) -> usize {
|
||||
self.reporter.on_checkout_start(url, rev)
|
||||
}
|
||||
|
||||
fn on_checkout_complete(&self, url: &Url, rev: &str, id: usize) {
|
||||
fn on_checkout_complete(&self, url: &DisplaySafeUrl, rev: &str, id: usize) {
|
||||
self.reporter.on_checkout_complete(url, rev, id);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use pubgrub::Ranges;
|
||||
use url::Url;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_torch::TorchBackend;
|
||||
|
||||
use crate::pubgrub::{PubGrubDependency, PubGrubPackage, PubGrubPackageInner};
|
||||
|
@ -21,7 +21,7 @@ impl SystemDependency {
|
|||
/// Extract a [`SystemDependency`] from an index URL.
|
||||
///
|
||||
/// For example, given `https://download.pytorch.org/whl/cu124`, returns CUDA 12.4.
|
||||
pub(super) fn from_index(index: &Url) -> Option<Self> {
|
||||
pub(super) fn from_index(index: &DisplaySafeUrl) -> Option<Self> {
|
||||
let backend = TorchBackend::from_index(index)?;
|
||||
let cuda_version = backend.cuda_version()?;
|
||||
Some(Self {
|
||||
|
@ -51,22 +51,21 @@ impl From<SystemDependency> for PubGrubDependency {
|
|||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
|
||||
use crate::resolver::system::SystemDependency;
|
||||
|
||||
#[test]
|
||||
fn pypi() {
|
||||
let url = Url::parse("https://pypi.org/simple").unwrap();
|
||||
let url = DisplaySafeUrl::parse("https://pypi.org/simple").unwrap();
|
||||
assert_eq!(SystemDependency::from_index(&url), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pytorch_cuda_12_4() {
|
||||
let url = Url::parse("https://download.pytorch.org/whl/cu124").unwrap();
|
||||
let url = DisplaySafeUrl::parse("https://download.pytorch.org/whl/cu124").unwrap();
|
||||
assert_eq!(
|
||||
SystemDependency::from_index(&url),
|
||||
Some(SystemDependency {
|
||||
|
@ -78,7 +77,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn pytorch_cpu() {
|
||||
let url = Url::parse("https://download.pytorch.org/whl/cpu").unwrap();
|
||||
let url = DisplaySafeUrl::parse("https://download.pytorch.org/whl/cpu").unwrap();
|
||||
assert_eq!(SystemDependency::from_index(&url), None);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue