mirror of https://github.com/astral-sh/uv
151 lines
6.3 KiB
Rust
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(())
|
|
}
|
|
}
|