uv/crates/uv-resolver/src/redirect.rs

151 lines
6.3 KiB
Rust

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 {
let ParsedUrl::Git(ParsedGitUrl {
url: git_url,
subdirectory,
}) = &url.parsed_url
else {
return url;
};
let Some(new_git_url) = git.precise(git_url.clone()) else {
if cfg!(debug_assertions) {
panic!("Unresolved Git URL: {}, {git_url:?}", url.verbatim);
} else {
return url;
}
};
let new_parsed_url = ParsedGitUrl {
url: new_git_url,
subdirectory: subdirectory.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),
verbatim: new_verbatim_url,
}
}
/// 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: DisplaySafeUrl) -> VerbatimUrl {
let redirect = VerbatimUrl::from_url(redirect);
// The redirect should be the "same" URL, but with a specific commit hash added after the `@`.
// We take advantage of this to preserve as much of the verbatim representation as possible.
if let Some(given) = url.given() {
let (given, fragment) = given
.split_once('#')
.map_or((given, None), |(prefix, suffix)| (prefix, Some(suffix)));
if let Some(precise_suffix) = redirect
.raw()
.path()
.rsplit_once('@')
.map(|(_, suffix)| suffix.to_owned())
{
// If there was an `@` in the original representation...
if let Some((.., parsed_suffix)) = url.raw().path().rsplit_once('@') {
if let Some((given_prefix, given_suffix)) = given.rsplit_once('@') {
// And the portion after the `@` is stable between the parsed and given representations...
if given_suffix == parsed_suffix {
// Preserve everything that precedes the `@` in the precise representation.
let given = format!("{given_prefix}@{precise_suffix}");
let given = if let Some(fragment) = fragment {
format!("{given}#{fragment}")
} else {
given
};
return redirect.with_given(given);
}
}
} else {
// If there was no `@` in the original representation, we can just append the
// precise suffix to the given representation.
let given = format!("{given}@{precise_suffix}");
let given = if let Some(fragment) = fragment {
format!("{given}#{fragment}")
} else {
given
};
return redirect.with_given(given);
}
}
}
redirect
}
#[cfg(test)]
mod tests {
use uv_pep508::{VerbatimUrl, VerbatimUrlError};
use uv_redacted::DisplaySafeUrl;
use crate::redirect::apply_redirect;
#[test]
fn test_apply_redirect() -> Result<(), VerbatimUrlError> {
// If there's no `@` in the original representation, we can just append the precise suffix
// to the given representation.
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git")?
.with_given("git+https://github.com/flask.git");
let redirect = DisplaySafeUrl::parse(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
)?;
let expected = VerbatimUrl::parse_url(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
)?
.with_given("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
assert_eq!(apply_redirect(&verbatim, redirect), expected);
// If there's an `@` in the original representation, and it's stable between the parsed and
// given representations, we preserve everything that precedes the `@` in the precise
// representation.
let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")?
.with_given("git+https://${DOMAIN}.com/flask.git@main");
let redirect = DisplaySafeUrl::parse(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
)?;
let expected = VerbatimUrl::parse_url(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
)?
.with_given("https://${DOMAIN}.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
assert_eq!(apply_redirect(&verbatim, redirect), expected);
// 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 = DisplaySafeUrl::parse(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
)?;
let expected = VerbatimUrl::parse_url(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
)?;
assert_eq!(apply_redirect(&verbatim, redirect), expected);
// 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 = DisplaySafeUrl::parse(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src",
)?;
let expected = VerbatimUrl::parse_url(
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src",
)?.with_given("git+https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src");
assert_eq!(apply_redirect(&verbatim, redirect), expected);
Ok(())
}
}