Skip to content

Commit e7f336a

Browse files
Preserve environment variables in resolved Git dependencies (#2125)
## Summary Closes #2116.
1 parent 5083f51 commit e7f336a

File tree

5 files changed

+93
-6
lines changed

5 files changed

+93
-6
lines changed

crates/pep508-rs/src/verbatim_url.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ impl VerbatimUrl {
9393

9494
/// Set the verbatim representation of the URL.
9595
#[must_use]
96-
pub fn with_given(self, given: String) -> Self {
96+
pub fn with_given(self, given: impl Into<String>) -> Self {
9797
Self {
98-
given: Some(given),
98+
given: Some(given.into()),
9999
..self
100100
}
101101
}

crates/uv-resolver/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod pins;
2727
mod prerelease_mode;
2828
mod pubgrub;
2929
mod python_requirement;
30+
mod redirect;
3031
mod resolution;
3132
mod resolution_mode;
3233
mod resolver;

crates/uv-resolver/src/redirect.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use url::Url;
2+
3+
use pep508_rs::VerbatimUrl;
4+
5+
/// Given a [`VerbatimUrl`] and a redirect, apply the redirect to the URL while preserving as much
6+
/// of the verbatim representation as possible.
7+
pub(crate) fn apply_redirect(url: &VerbatimUrl, redirect: &Url) -> VerbatimUrl {
8+
let redirect = VerbatimUrl::from_url(redirect.clone());
9+
10+
// The redirect should be the "same" URL, but with a specific commit hash added after the `@`.
11+
// We take advantage of this to preserve as much of the verbatim representation as possible.
12+
if let Some(given) = url.given() {
13+
if let Some(precise_suffix) = redirect
14+
.raw()
15+
.path()
16+
.rsplit_once('@')
17+
.map(|(_, suffix)| suffix.to_owned())
18+
{
19+
// If there was an `@` in the original representation...
20+
if let Some((.., parsed_suffix)) = url.raw().path().rsplit_once('@') {
21+
if let Some((given_prefix, given_suffix)) = given.rsplit_once('@') {
22+
// And the portion after the `@` is stable between the parsed and given representations...
23+
if given_suffix == parsed_suffix {
24+
// Preserve everything that precedes the `@` in the precise representation.
25+
return redirect.with_given(format!("{given_prefix}@{precise_suffix}"));
26+
}
27+
}
28+
} else {
29+
// If there was no `@` in the original representation, we can just append the
30+
// precise suffix to the given representation.
31+
return redirect.with_given(format!("{given}@{precise_suffix}"));
32+
}
33+
}
34+
}
35+
36+
redirect
37+
}
38+
39+
#[cfg(test)]
40+
mod tests {
41+
use super::*;
42+
43+
#[test]
44+
fn test_apply_redirect() -> Result<(), url::ParseError> {
45+
// If there's no `@` in the original representation, we can just append the precise suffix
46+
// to the given representation.
47+
let verbatim = VerbatimUrl::parse("https://github.com/flask.git")?
48+
.with_given("git+https://github.com/flask.git");
49+
let redirect =
50+
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
51+
52+
let expected = VerbatimUrl::parse(
53+
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
54+
)?
55+
.with_given("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
56+
assert_eq!(apply_redirect(&verbatim, &redirect), expected);
57+
58+
// If there's an `@` in the original representation, and it's stable between the parsed and
59+
// given representations, we preserve everything that precedes the `@` in the precise
60+
// representation.
61+
let verbatim = VerbatimUrl::parse("https://github.com/flask.git@main")?
62+
.with_given("git+https://${DOMAIN}.com/flask.git@main");
63+
let redirect =
64+
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
65+
66+
let expected = VerbatimUrl::parse(
67+
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
68+
)?
69+
.with_given("https://${DOMAIN}.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe");
70+
assert_eq!(apply_redirect(&verbatim, &redirect), expected);
71+
72+
// If there's a conflict after the `@`, discard the original representation.
73+
let verbatim = VerbatimUrl::parse("https://github.com/flask.git@main")?
74+
.with_given("git+https://github.com/flask.git@${TAG}".to_string());
75+
let redirect =
76+
Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?;
77+
78+
let expected = VerbatimUrl::parse(
79+
"https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe",
80+
)?;
81+
assert_eq!(apply_redirect(&verbatim, &redirect), expected);
82+
83+
Ok(())
84+
}
85+
}

crates/uv-resolver/src/resolution.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ use url::Url;
1515
use distribution_types::{Dist, DistributionMetadata, LocalEditable, Name, PackageId, Verbatim};
1616
use once_map::OnceMap;
1717
use pep440_rs::Version;
18-
use pep508_rs::VerbatimUrl;
1918
use pypi_types::{Hashes, Metadata21};
2019
use uv_normalize::{ExtraName, PackageName};
2120

2221
use crate::editables::Editables;
2322
use crate::pins::FilePins;
2423
use crate::pubgrub::{PubGrubDistribution, PubGrubPackage, PubGrubPriority};
24+
use crate::redirect::apply_redirect;
2525
use crate::resolver::VersionsResponse;
2626
use crate::ResolveError;
2727

@@ -106,7 +106,7 @@ impl ResolutionGraph {
106106
} else {
107107
let url = redirects.get(url).map_or_else(
108108
|| url.clone(),
109-
|url| VerbatimUrl::unknown(url.value().clone()),
109+
|precise| apply_redirect(url, precise.value()),
110110
);
111111
Dist::from_url(package_name.clone(), url)?
112112
};
@@ -188,7 +188,7 @@ impl ResolutionGraph {
188188
if !metadata.provides_extras.contains(extra) {
189189
let url = redirects.get(url).map_or_else(
190190
|| url.clone(),
191-
|url| VerbatimUrl::unknown(url.value().clone()),
191+
|precise| apply_redirect(url, precise.value()),
192192
);
193193
let pinned_package = Dist::from_url(package_name.clone(), url)?;
194194

crates/uv-traits/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::path::{Path, PathBuf};
88
use std::str::FromStr;
99

1010
use anyhow::Result;
11-
use serde::ser::SerializeMap;
1211

1312
use distribution_types::{CachedDist, DistributionId, IndexLocations, Resolution, SourceDist};
1413
use once_map::OnceMap;
@@ -361,6 +360,8 @@ impl ConfigSettings {
361360
#[cfg(feature = "serde")]
362361
impl serde::Serialize for ConfigSettings {
363362
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
363+
use serde::ser::SerializeMap;
364+
364365
let mut map = serializer.serialize_map(Some(self.0.len()))?;
365366
for (key, value) in &self.0 {
366367
match value {

0 commit comments

Comments
 (0)