mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Allow conflicting Git URLs that refer to the same commit SHA (#2769)
## Summary This PR leverages our lookahead direct URL resolution to significantly improve the range of Git URLs that we can accept (e.g., if a user provides the same requirement, once as a direct dependency, and once as a tag). We did some of this in #2285, but the solution here is more general and works for arbitrary transitive URLs. Closes https://github.com/astral-sh/uv/issues/2614.
This commit is contained in:
parent
20d4762776
commit
c30a65ee0c
9 changed files with 267 additions and 158 deletions
|
@ -91,6 +91,12 @@ impl Hash for CanonicalUrl {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<CanonicalUrl> for Url {
|
||||
fn from(value: CanonicalUrl) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CanonicalUrl {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0, f)
|
||||
|
|
|
@ -8,6 +8,7 @@ use rustc_hash::FxHashMap;
|
|||
use tracing::debug;
|
||||
use url::Url;
|
||||
|
||||
use cache_key::CanonicalUrl;
|
||||
use distribution_types::DirectGitUrl;
|
||||
use uv_cache::{Cache, CacheBucket};
|
||||
use uv_fs::LockedFile;
|
||||
|
@ -39,7 +40,7 @@ pub(crate) async fn fetch_git_archive(
|
|||
fs::create_dir_all(&lock_dir)
|
||||
.await
|
||||
.map_err(Error::CacheWrite)?;
|
||||
let canonical_url = cache_key::CanonicalUrl::new(url);
|
||||
let canonical_url = CanonicalUrl::new(url);
|
||||
let _lock = LockedFile::acquire(
|
||||
lock_dir.join(cache_key::digest(&canonical_url)),
|
||||
&canonical_url,
|
||||
|
@ -91,9 +92,8 @@ pub(crate) async fn resolve_precise(
|
|||
cache: &Cache,
|
||||
reporter: Option<&Arc<dyn Reporter>>,
|
||||
) -> Result<Option<Url>, Error> {
|
||||
let git_dir = cache.bucket(CacheBucket::Git);
|
||||
|
||||
let DirectGitUrl { url, subdirectory } = DirectGitUrl::try_from(url).map_err(Error::Git)?;
|
||||
let url = Url::from(CanonicalUrl::new(url));
|
||||
let DirectGitUrl { url, subdirectory } = DirectGitUrl::try_from(&url).map_err(Error::Git)?;
|
||||
|
||||
// If the Git reference already contains a complete SHA, short-circuit.
|
||||
if url.precise().is_some() {
|
||||
|
@ -111,6 +111,8 @@ pub(crate) async fn resolve_precise(
|
|||
}
|
||||
}
|
||||
|
||||
let git_dir = cache.bucket(CacheBucket::Git);
|
||||
|
||||
// Fetch the precise SHA of the Git reference (which could be a branch, a tag, a partial
|
||||
// commit, etc.).
|
||||
let source = if let Some(reporter) = reporter {
|
||||
|
@ -135,3 +137,134 @@ pub(crate) async fn resolve_precise(
|
|||
subdirectory,
|
||||
})))
|
||||
}
|
||||
|
||||
/// Returns `true` if the URLs refer to the same Git commit.
|
||||
///
|
||||
/// For example, the previous URL could be a branch or tag, while the current URL would be a
|
||||
/// precise commit hash.
|
||||
pub fn is_same_reference<'a>(a: &'a Url, b: &'a Url) -> bool {
|
||||
let resolved_git_refs = RESOLVED_GIT_REFS.lock().unwrap();
|
||||
is_same_reference_impl(a, b, &resolved_git_refs)
|
||||
}
|
||||
|
||||
/// Returns `true` if the URLs refer to the same Git commit.
|
||||
///
|
||||
/// Like [`is_same_reference`], but accepts a resolved reference cache for testing.
|
||||
fn is_same_reference_impl<'a>(
|
||||
a: &'a Url,
|
||||
b: &'a Url,
|
||||
resolved_refs: &FxHashMap<GitUrl, GitUrl>,
|
||||
) -> bool {
|
||||
// Convert `a` to a Git URL, if possible.
|
||||
let Ok(a_git) = DirectGitUrl::try_from(&Url::from(CanonicalUrl::new(a))) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Convert `b` to a Git URL, if possible.
|
||||
let Ok(b_git) = DirectGitUrl::try_from(&Url::from(CanonicalUrl::new(b))) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// The URLs must refer to the same subdirectory, if any.
|
||||
if a_git.subdirectory != b_git.subdirectory {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The URLs must refer to the same repository.
|
||||
if a_git.url.repository() != b_git.url.repository() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the URLs have the same tag, they refer to the same commit.
|
||||
if a_git.url.reference() == b_git.url.reference() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, the URLs must resolve to the same precise commit.
|
||||
let Some(a_precise) = a_git
|
||||
.url
|
||||
.precise()
|
||||
.or_else(|| resolved_refs.get(&a_git.url).and_then(GitUrl::precise))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(b_precise) = b_git
|
||||
.url
|
||||
.precise()
|
||||
.or_else(|| resolved_refs.get(&b_git.url).and_then(GitUrl::precise))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
a_precise == b_precise
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
use url::Url;
|
||||
|
||||
use uv_git::GitUrl;
|
||||
|
||||
#[test]
|
||||
fn same_reference() -> Result<()> {
|
||||
let empty = FxHashMap::default();
|
||||
|
||||
// Same repository, same tag.
|
||||
let a = Url::parse("git+https://example.com/MyProject.git@main")?;
|
||||
let b = Url::parse("git+https://example.com/MyProject.git@main")?;
|
||||
assert!(super::is_same_reference_impl(&a, &b, &empty));
|
||||
|
||||
// Same repository, same tag, same subdirectory.
|
||||
let a = Url::parse("git+https://example.com/MyProject.git@main#subdirectory=pkg_dir")?;
|
||||
let b = Url::parse("git+https://example.com/MyProject.git@main#subdirectory=pkg_dir")?;
|
||||
assert!(super::is_same_reference_impl(&a, &b, &empty));
|
||||
|
||||
// Different repositories, same tag.
|
||||
let a = Url::parse("git+https://example.com/MyProject.git@main")?;
|
||||
let b = Url::parse("git+https://example.com/MyOtherProject.git@main")?;
|
||||
assert!(!super::is_same_reference_impl(&a, &b, &empty));
|
||||
|
||||
// Same repository, different tags.
|
||||
let a = Url::parse("git+https://example.com/MyProject.git@main")?;
|
||||
let b = Url::parse("git+https://example.com/MyProject.git@v1.0")?;
|
||||
assert!(!super::is_same_reference_impl(&a, &b, &empty));
|
||||
|
||||
// Same repository, same tag, different subdirectory.
|
||||
let a = Url::parse("git+https://example.com/MyProject.git@main#subdirectory=pkg_dir")?;
|
||||
let b = Url::parse("git+https://example.com/MyProject.git@main#subdirectory=other_dir")?;
|
||||
assert!(!super::is_same_reference_impl(&a, &b, &empty));
|
||||
|
||||
// Same repository, different tags, but same precise commit.
|
||||
let a = Url::parse("git+https://example.com/MyProject.git@main")?;
|
||||
let b = Url::parse(
|
||||
"git+https://example.com/MyProject.git@164a8735b081663fede48c5041667b194da15d25",
|
||||
)?;
|
||||
let mut resolved_refs = FxHashMap::default();
|
||||
resolved_refs.insert(
|
||||
GitUrl::try_from(Url::parse("https://example.com/MyProject@main")?)?,
|
||||
GitUrl::try_from(Url::parse(
|
||||
"https://example.com/MyProject@164a8735b081663fede48c5041667b194da15d25",
|
||||
)?)?,
|
||||
);
|
||||
assert!(super::is_same_reference_impl(&a, &b, &resolved_refs));
|
||||
|
||||
// Same repository, different tags, different precise commit.
|
||||
let a = Url::parse("git+https://example.com/MyProject.git@main")?;
|
||||
let b = Url::parse(
|
||||
"git+https://example.com/MyProject.git@164a8735b081663fede48c5041667b194da15d25",
|
||||
)?;
|
||||
let mut resolved_refs = FxHashMap::default();
|
||||
resolved_refs.insert(
|
||||
GitUrl::try_from(Url::parse("https://example.com/MyProject@main")?)?,
|
||||
GitUrl::try_from(Url::parse(
|
||||
"https://example.com/MyProject@f2c9e88f3ec9526bbcec68d150b176d96a750aba",
|
||||
)?)?,
|
||||
);
|
||||
assert!(!super::is_same_reference_impl(&a, &b, &resolved_refs));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub use distribution_database::DistributionDatabase;
|
||||
pub use download::{BuiltWheel, DiskWheel, LocalWheel};
|
||||
pub use error::Error;
|
||||
pub use git::is_same_reference;
|
||||
pub use index::{BuiltWheelIndex, RegistryWheelIndex};
|
||||
pub use reporter::Reporter;
|
||||
pub use source::{download_and_extract_archive, SourceDistributionBuilder};
|
||||
|
|
|
@ -47,6 +47,11 @@ impl GitUrl {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the reference is a full commit.
|
||||
pub fn is_full_commit(&self) -> bool {
|
||||
matches!(self.reference, GitReference::FullCommit(_))
|
||||
}
|
||||
|
||||
/// Return the precise commit, if known.
|
||||
pub fn precise(&self) -> Option<GitSha> {
|
||||
self.precise
|
||||
|
|
|
@ -96,9 +96,8 @@ impl<'a, Context: BuildContext + Send + Sync> LookaheadResolver<'a, Context> {
|
|||
|
||||
while !queue.is_empty() || !futures.is_empty() {
|
||||
while let Some(requirement) = queue.pop_front() {
|
||||
// Ignore duplicates. If we have conflicting URLs, we'll catch that later.
|
||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||
if seen.insert(requirement.name.clone()) {
|
||||
if seen.insert(requirement.clone()) {
|
||||
futures.push(self.lookahead(requirement));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ fn to_pubgrub(
|
|||
));
|
||||
};
|
||||
|
||||
if !urls.is_allowed(expected, url) {
|
||||
if !Urls::is_allowed(expected, url) {
|
||||
return Err(ResolveError::ConflictingUrlsTransitive(
|
||||
requirement.name.clone(),
|
||||
expected.verbatim().to_string(),
|
||||
|
|
|
@ -3,36 +3,28 @@ use tracing::debug;
|
|||
|
||||
use distribution_types::Verbatim;
|
||||
use pep508_rs::{MarkerEnvironment, VerbatimUrl};
|
||||
use uv_distribution::is_same_reference;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::{Manifest, ResolveError};
|
||||
|
||||
/// A map of package names to their associated, required URLs.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Urls {
|
||||
/// A map of package names to their associated, required URLs.
|
||||
required: FxHashMap<PackageName, VerbatimUrl>,
|
||||
/// A map from required URL to URL that is assumed to be a less precise variant.
|
||||
allowed: FxHashMap<VerbatimUrl, VerbatimUrl>,
|
||||
}
|
||||
pub(crate) struct Urls(FxHashMap<PackageName, VerbatimUrl>);
|
||||
|
||||
impl Urls {
|
||||
pub(crate) fn from_manifest(
|
||||
manifest: &Manifest,
|
||||
markers: &MarkerEnvironment,
|
||||
) -> Result<Self, ResolveError> {
|
||||
let mut required: FxHashMap<PackageName, VerbatimUrl> = FxHashMap::default();
|
||||
let mut allowed: FxHashMap<VerbatimUrl, VerbatimUrl> = FxHashMap::default();
|
||||
let mut urls: FxHashMap<PackageName, VerbatimUrl> = FxHashMap::default();
|
||||
|
||||
// Add the themselves to the list of required URLs.
|
||||
for (editable, metadata) in &manifest.editables {
|
||||
if let Some(previous) = required.insert(metadata.name.clone(), editable.url.clone()) {
|
||||
if let Some(previous) = urls.insert(metadata.name.clone(), editable.url.clone()) {
|
||||
if !is_equal(&previous, &editable.url) {
|
||||
if is_precise(&previous, &editable.url) {
|
||||
debug!(
|
||||
"Assuming {} is a precise variant of {previous}",
|
||||
editable.url
|
||||
);
|
||||
allowed.insert(editable.url.clone(), previous);
|
||||
if is_same_reference(&previous, &editable.url) {
|
||||
debug!("Allowing {} as a variant of {previous}", editable.url);
|
||||
} else {
|
||||
return Err(ResolveError::ConflictingUrlsDirect(
|
||||
metadata.name.clone(),
|
||||
|
@ -47,47 +39,38 @@ impl Urls {
|
|||
// Add all direct requirements and constraints. If there are any conflicts, return an error.
|
||||
for requirement in manifest.requirements(markers) {
|
||||
if let Some(pep508_rs::VersionOrUrl::Url(url)) = &requirement.version_or_url {
|
||||
if let Some(previous) = required.insert(requirement.name.clone(), url.clone()) {
|
||||
if is_equal(&previous, url) {
|
||||
continue;
|
||||
if let Some(previous) = urls.insert(requirement.name.clone(), url.clone()) {
|
||||
if !is_equal(&previous, url) {
|
||||
if is_same_reference(&previous, url) {
|
||||
debug!("Allowing {url} as a variant of {previous}");
|
||||
} else {
|
||||
return Err(ResolveError::ConflictingUrlsDirect(
|
||||
requirement.name.clone(),
|
||||
previous.verbatim().to_string(),
|
||||
url.verbatim().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if is_precise(&previous, url) {
|
||||
debug!("Assuming {url} is a precise variant of {previous}");
|
||||
allowed.insert(url.clone(), previous);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(ResolveError::ConflictingUrlsDirect(
|
||||
requirement.name.clone(),
|
||||
previous.verbatim().to_string(),
|
||||
url.verbatim().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { required, allowed })
|
||||
Ok(Self(urls))
|
||||
}
|
||||
|
||||
/// Return the [`VerbatimUrl`] associated with the given package name, if any.
|
||||
pub(crate) fn get(&self, package: &PackageName) -> Option<&VerbatimUrl> {
|
||||
self.required.get(package)
|
||||
self.0.get(package)
|
||||
}
|
||||
|
||||
/// Returns `true` if the provided URL is compatible with the given "allowed" URL.
|
||||
pub(crate) fn is_allowed(&self, expected: &VerbatimUrl, provided: &VerbatimUrl) -> bool {
|
||||
pub(crate) fn is_allowed(expected: &VerbatimUrl, provided: &VerbatimUrl) -> bool {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
if is_equal(expected, provided) {
|
||||
// If the URLs are canonically equivalent, they're compatible.
|
||||
true
|
||||
} else if self
|
||||
.allowed
|
||||
.get(expected)
|
||||
.is_some_and(|allowed| is_equal(allowed, provided))
|
||||
{
|
||||
// If the URL is canonically equivalent to the imprecise variant of the URL, they're
|
||||
// compatible.
|
||||
} else if is_same_reference(expected, provided) {
|
||||
// If the URLs refer to the same commit, they're compatible.
|
||||
true
|
||||
} else {
|
||||
// Otherwise, they're incompatible.
|
||||
|
@ -103,53 +86,6 @@ fn is_equal(previous: &VerbatimUrl, url: &VerbatimUrl) -> bool {
|
|||
cache_key::CanonicalUrl::new(previous.raw()) == cache_key::CanonicalUrl::new(url.raw())
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`VerbatimUrl`] appears to be a more precise variant of the previous
|
||||
/// [`VerbatimUrl`].
|
||||
///
|
||||
/// Primarily, this method intends to accept URLs that map to the same repository, but with a
|
||||
/// precise Git commit hash overriding a looser tag or branch. For example, if the previous URL
|
||||
/// is `git+https://github.com/pallets/werkzeug.git@main`, this method would accept
|
||||
/// `git+https://github.com/pallets/werkzeug@32e69512134c2f8183c6438b2b2e13fd24e9d19f`, and
|
||||
/// assume that the latter is a more precise variant of the former. This is particularly useful
|
||||
/// for workflows in which the output of `uv pip compile` is used as an input constraint on a
|
||||
/// subsequent resolution, since `uv` will pin the exact commit hash of the package.
|
||||
fn is_precise(previous: &VerbatimUrl, url: &VerbatimUrl) -> bool {
|
||||
if cache_key::RepositoryUrl::new(previous.raw()) != cache_key::RepositoryUrl::new(url.raw()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there's no tag in the overriding URL, consider it incompatible.
|
||||
let Some(url_tag) = url
|
||||
.raw()
|
||||
.path()
|
||||
.rsplit_once('@')
|
||||
.map(|(_prefix, suffix)| suffix)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Accept the overriding URL, as long as it's a full commit hash...
|
||||
let url_is_commit = url_tag.len() == 40 && url_tag.chars().all(|ch| ch.is_ascii_hexdigit());
|
||||
if !url_is_commit {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there's no tag in the previous URL, consider it compatible.
|
||||
let Some(previous_tag) = previous
|
||||
.raw()
|
||||
.path()
|
||||
.rsplit_once('@')
|
||||
.map(|(_prefix, suffix)| suffix)
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
|
||||
// If the previous URL is a full commit hash, consider it incompatible.
|
||||
let previous_is_commit =
|
||||
previous_tag.len() == 40 && previous_tag.chars().all(|ch| ch.is_ascii_hexdigit());
|
||||
!previous_is_commit
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -183,37 +119,4 @@ mod tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_precision() -> Result<(), url::ParseError> {
|
||||
// Same repository, no tag on the previous URL, non-SHA on the overriding URL.
|
||||
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git")?;
|
||||
let url = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||
assert!(!is_precise(&previous, &url));
|
||||
|
||||
// Same repository, no tag on the previous URL, SHA on the overriding URL.
|
||||
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git")?;
|
||||
let url = VerbatimUrl::parse_url(
|
||||
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
||||
)?;
|
||||
assert!(is_precise(&previous, &url));
|
||||
|
||||
// Same repository, tag on the previous URL, SHA on the overriding URL.
|
||||
let previous = VerbatimUrl::parse_url("git+https://example.com/MyProject.git@v1.0")?;
|
||||
let url = VerbatimUrl::parse_url(
|
||||
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
||||
)?;
|
||||
assert!(is_precise(&previous, &url));
|
||||
|
||||
// Same repository, SHA on the previous URL, different SHA on the overriding URL.
|
||||
let previous = VerbatimUrl::parse_url(
|
||||
"git+https://example.com/MyProject.git@5ae5980c885e350a34ca019a84ba14a2a228d262",
|
||||
)?;
|
||||
let url = VerbatimUrl::parse_url(
|
||||
"git+https://example.com/MyProject.git@c3cd550a7a7c41b2c286ca52fbb6dec5fea195ef",
|
||||
)?;
|
||||
assert!(!is_precise(&previous, &url));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1578,6 +1578,7 @@ fn conflicting_repeated_url_dependency_markers() -> Result<()> {
|
|||
fn conflicting_repeated_url_dependency_version_match() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
|
||||
requirements_in.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@2.0.0\nwerkzeug @ https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl")?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
|
@ -1621,12 +1622,15 @@ fn conflicting_transitive_url_dependency() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Request Werkzeug via two different URLs which resolve to the same canonical version.
|
||||
/// Request `anyio` via two different URLs which resolve to the same canonical version.
|
||||
#[test]
|
||||
fn compatible_repeated_url_dependency() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@2.0.0\nwerkzeug @ git+https://github.com/pallets/werkzeug@2.0.0")?;
|
||||
requirements_in.write_str(indoc! {r"
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
anyio @ git+https://github.com/agronholm/anyio@4.3.0
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
|
@ -1635,23 +1639,30 @@ fn compatible_repeated_url_dependency() -> Result<()> {
|
|||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
werkzeug @ git+https://github.com/pallets/werkzeug@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
idna==3.6
|
||||
# via anyio
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Resolved 3 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Request Werkzeug via two different URLs which resolve to the same repository, but different
|
||||
/// Request `anyio` via two different URLs which resolve to the same repository, but different
|
||||
/// commits.
|
||||
#[test]
|
||||
fn conflicting_repeated_url_dependency() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@2.0.0\nwerkzeug @ git+https://github.com/pallets/werkzeug@3.0.0")?;
|
||||
requirements_in.write_str(indoc! {r"
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.0.0
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
|
@ -1660,22 +1671,26 @@ fn conflicting_repeated_url_dependency() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Requirements contain conflicting URLs for package `werkzeug`:
|
||||
- git+https://github.com/pallets/werkzeug.git@2.0.0
|
||||
- git+https://github.com/pallets/werkzeug@3.0.0
|
||||
error: Requirements contain conflicting URLs for package `anyio`:
|
||||
- git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
- git+https://github.com/agronholm/anyio.git@4.0.0
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Request Werkzeug via two different URLs: `main`, and a precise SHA. Allow the precise SHA
|
||||
/// to override the `main` branch.
|
||||
/// Request Werkzeug via two different URLs: `3.0.1`, and a precise SHA. Allow the precise SHA
|
||||
/// to override the `3.0.1` branch.
|
||||
#[test]
|
||||
fn compatible_narrowed_url_dependency() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@main\nwerkzeug @ git+https://github.com/pallets/werkzeug@32e69512134c2f8183c6438b2b2e13fd24e9d19f")?;
|
||||
requirements_in.write_str(indoc! {r"
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e31
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
|
@ -1684,49 +1699,96 @@ fn compatible_narrowed_url_dependency() -> Result<()> {
|
|||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
markupsafe==2.1.5
|
||||
# via werkzeug
|
||||
werkzeug @ git+https://github.com/pallets/werkzeug@32e69512134c2f8183c6438b2b2e13fd24e9d19f
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
idna==3.6
|
||||
# via anyio
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Resolved 3 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Request Werkzeug via two different URLs: `main`, and a precise SHA, followed by `main` again.
|
||||
/// We _may_ want to allow this, but we don't right now.
|
||||
/// Request Werkzeug via two different URLs: a precise SHA, and `3.0.1`. Allow the precise SHA
|
||||
/// to override the `3.0.1` branch.
|
||||
#[test]
|
||||
fn compatible_broader_url_dependency() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str(indoc! {r"
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e31
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
idna==3.6
|
||||
# via anyio
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Request Werkzeug via two different URLs: `4.3.0`, and a precise SHA, followed by `4.3.0` again.
|
||||
#[test]
|
||||
fn compatible_repeated_narrowed_url_dependency() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("werkzeug @ git+https://github.com/pallets/werkzeug@main\nwerkzeug @ git+https://github.com/pallets/werkzeug.git@main\nwerkzeug @ git+https://github.com/pallets/werkzeug@32e69512134c2f8183c6438b2b2e13fd24e9d19f\nwerkzeug @ git+https://github.com/pallets/werkzeug.git@main")?;
|
||||
requirements_in.write_str(indoc! {r"
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
idna==3.6
|
||||
# via anyio
|
||||
sniffio==1.3.1
|
||||
# via anyio
|
||||
|
||||
----- stderr -----
|
||||
error: Requirements contain conflicting URLs for package `werkzeug`:
|
||||
- git+https://github.com/pallets/werkzeug@32e69512134c2f8183c6438b2b2e13fd24e9d19f
|
||||
- git+https://github.com/pallets/werkzeug.git@main
|
||||
Resolved 3 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Request Werkzeug via two different URLs: `main`, and a precise SHA. Allow the precise SHA
|
||||
/// to override the `main` branch, but error when we see yet another URL for the same package.
|
||||
/// Request Werkzeug via two different URLs: `master`, and a precise SHA. Allow the precise SHA
|
||||
/// to override the `master` branch, but error when we see yet another URL for the same package.
|
||||
#[test]
|
||||
fn incompatible_narrowed_url_dependency() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let requirements_in = context.temp_dir.child("requirements.in");
|
||||
requirements_in.write_str("werkzeug @ git+https://github.com/pallets/werkzeug.git@main\nwerkzeug @ git+https://github.com/pallets/werkzeug@32e69512134c2f8183c6438b2b2e13fd24e9d19f\nwerkzeug @ git+https://github.com/pallets/werkzeug.git@3.0.1")?;
|
||||
requirements_in.write_str(indoc! {r"
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@master
|
||||
anyio @ git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
anyio @ git+https://github.com/agronholm/anyio.git@4.3.0
|
||||
"})?;
|
||||
|
||||
uv_snapshot!(context.compile()
|
||||
.arg("requirements.in"), @r###"
|
||||
|
@ -1735,9 +1797,9 @@ fn incompatible_narrowed_url_dependency() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Requirements contain conflicting URLs for package `werkzeug`:
|
||||
- git+https://github.com/pallets/werkzeug@32e69512134c2f8183c6438b2b2e13fd24e9d19f
|
||||
- git+https://github.com/pallets/werkzeug.git@3.0.1
|
||||
error: Requirements contain conflicting URLs for package `anyio`:
|
||||
- git+https://github.com/agronholm/anyio.git@master
|
||||
- git+https://github.com/agronholm/anyio@437a7e310925a962cab4a58fcd2455fbcd578d51
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
@ -582,7 +582,7 @@ fn install_git_tag() -> Result<()> {
|
|||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug.git@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
+ werkzeug==2.0.0 (from git+https://github.com/pallets/werkzeug@af160e0b6b7ddd81c22f1652c728ff5ac72d5c74)
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue