From 62e5b800ccf87831e2ddf457fc6d73aea827072c Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Mon, 8 Jun 2026 19:02:03 +0100 Subject: [PATCH 1/2] Reject Git revisions in uv upgrade --- crates/uv/src/commands/project/upgrade.rs | 28 ++++++++++++++----- crates/uv/tests/it/upgrade.rs | 33 +++++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/crates/uv/src/commands/project/upgrade.rs b/crates/uv/src/commands/project/upgrade.rs index 3acf68ce233ee..34843de7fefb6 100644 --- a/crates/uv/src/commands/project/upgrade.rs +++ b/crates/uv/src/commands/project/upgrade.rs @@ -11,7 +11,7 @@ use uv_distribution::{ArchiveMetadata, Metadata}; use uv_distribution_types::Identifier; use uv_normalize::PackageName; use uv_pep440::{Operator, VersionSpecifier, VersionSpecifiers}; -use uv_pep508::{Requirement, VerbatimUrl, VersionOrUrl}; +use uv_pep508::{MarkerTree, Requirement, VerbatimUrl, VersionOrUrl}; use uv_preview::Preview; use uv_pypi_types::{PyProjectToml, ResolutionMetadata, VerbatimParsedUrl}; use uv_python::{PythonDownloads, PythonPreference}; @@ -112,14 +112,19 @@ pub(crate) async fn upgrade( .and_then(|uv| uv.sources.as_ref()) .and_then(|sources| sources.inner().get(&package)) .or_else(|| project.workspace().sources().get(&package)); - let extra = requirement.marker.top_level_extra_name(); if sources.is_some_and(|sources| { sources.iter().any(|source| { - source - .extra() - .is_none_or(|target| extra.as_deref() == Some(target)) - && source.group().is_none() - && !source.marker().is_disjoint(requirement.marker) + source_is_applicable(source, requirement.marker) + && matches!(source, Source::Git { rev: Some(_), .. }) + }) + }) { + bail!( + "Dependency `{package}` is pinned to a Git revision and cannot be upgraded commit-to-commit" + ); + } + if sources.is_some_and(|sources| { + sources.iter().any(|source| { + source_is_applicable(source, requirement.marker) && !matches!(source, Source::Registry { .. }) }) }) { @@ -270,6 +275,15 @@ pub(crate) async fn upgrade( Ok(ExitStatus::Success) } +fn source_is_applicable(source: &Source, requirement_marker: MarkerTree) -> bool { + let extra = requirement_marker.top_level_extra_name(); + source + .extra() + .is_none_or(|target| extra.as_deref() == Some(target)) + && source.group().is_none() + && !source.marker().is_disjoint(requirement_marker) +} + fn relax_requirement( requirement: &Requirement, ) -> Requirement { diff --git a/crates/uv/tests/it/upgrade.rs b/crates/uv/tests/it/upgrade.rs index 59d77b5d8e9e3..02bd6859633d1 100644 --- a/crates/uv/tests/it/upgrade.rs +++ b/crates/uv/tests/it/upgrade.rs @@ -439,6 +439,39 @@ fn upgrade_rejects_self_dependency() -> Result<()> { assert_project_unchanged(&context, pyproject_toml) } +#[test] +fn upgrade_rejects_git_revision() -> Result<()> { + let context = uv_test::test_context_with_versions!(&[]); + let pyproject_toml = r#" + [project] + name = "example" + version = "0.1.0" + dependencies = ["requests>=2"] + + [tool.uv.sources] + requests = { git = "https://github.com/psf/requests", rev = "main" } + "#; + context + .temp_dir + .child("pyproject.toml") + .write_str(pyproject_toml)?; + + uv_snapshot!( + context.filters(), + context.upgrade().arg("requests"), + @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Dependency `requests` is pinned to a Git revision and cannot be upgraded commit-to-commit + " + ); + + assert_project_unchanged(&context, pyproject_toml) +} + #[test] fn upgrade_rejects_non_registry_sources() -> Result<()> { let context = uv_test::test_context_with_versions!(&[]); From 5c692fea03ec990b788c200e08595440cc46a445 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Mon, 8 Jun 2026 23:44:40 +0100 Subject: [PATCH 2/2] Use a commit hash in Git revision test --- crates/uv/tests/it/upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/tests/it/upgrade.rs b/crates/uv/tests/it/upgrade.rs index 02bd6859633d1..c5c4a7168da6c 100644 --- a/crates/uv/tests/it/upgrade.rs +++ b/crates/uv/tests/it/upgrade.rs @@ -449,7 +449,7 @@ fn upgrade_rejects_git_revision() -> Result<()> { dependencies = ["requests>=2"] [tool.uv.sources] - requests = { git = "https://github.com/psf/requests", rev = "main" } + requests = { git = "https://github.com/psf/requests", rev = "6f205ff422bccd5e4c4fc0b64c5f3e7df5181db6" } "#; context .temp_dir