Include SHA when listing lockfile changes (#15817)
## Summary Right now, we only list changes if the _version_ differs. This PR takes the SHA into account. We may want to list changes to _any_ sources, but that gets more complicated (e.g., if the user swaps the index URL, we'd have to show _all_ changes to the index URL). Closes #15810.
This commit is contained in:
@@ -25,6 +25,11 @@ impl GitOid {
|
||||
pub fn as_short_str(&self) -> &str {
|
||||
&self.as_str()[..16]
|
||||
}
|
||||
|
||||
/// Return a (very) truncated representation, i.e., the first 8 characters of the SHA.
|
||||
pub fn as_tiny_str(&self) -> &str {
|
||||
&self.as_str()[..8]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
|
||||
@@ -3058,6 +3058,14 @@ impl Package {
|
||||
self.id.version.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the Git SHA of the package, if it is a Git source.
|
||||
pub fn git_sha(&self) -> Option<&GitOid> {
|
||||
match &self.id.source {
|
||||
Source::Git(_, git) => Some(&git.precise),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the fork markers for this package, if any.
|
||||
pub fn fork_markers(&self) -> &[UniversalMarker] {
|
||||
self.fork_markers.as_slice()
|
||||
|
||||
@@ -22,6 +22,7 @@ use uv_distribution_types::{
|
||||
Requirement, RequiresPython, UnresolvedRequirementSpecification,
|
||||
};
|
||||
use uv_git::ResolvedRepositoryReference;
|
||||
use uv_git_types::GitOid;
|
||||
use uv_normalize::{GroupName, PackageName};
|
||||
use uv_pep440::Version;
|
||||
use uv_preview::{Preview, PreviewFeatures};
|
||||
@@ -30,7 +31,7 @@ use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreferenc
|
||||
use uv_requirements::ExtrasResolver;
|
||||
use uv_requirements::upgrade::{LockedRequirements, read_lock_requirements};
|
||||
use uv_resolver::{
|
||||
FlatIndex, InMemoryIndex, Lock, Options, OptionsBuilder, PythonRequirement,
|
||||
FlatIndex, InMemoryIndex, Lock, Options, OptionsBuilder, Package, PythonRequirement,
|
||||
ResolverEnvironment, ResolverManifest, SatisfiesResult, UniversalMarker,
|
||||
};
|
||||
use uv_scripts::Pep723Script;
|
||||
@@ -1355,28 +1356,56 @@ impl ValidatedLock {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct LockEventVersion<'lock> {
|
||||
/// The version of the package, or `None` if the package has a dynamic version.
|
||||
version: Option<&'lock Version>,
|
||||
/// The short Git SHA of the package, if it was installed from a Git repository.
|
||||
sha: Option<&'lock str>,
|
||||
}
|
||||
|
||||
impl<'lock> From<&'lock Package> for LockEventVersion<'lock> {
|
||||
fn from(value: &'lock Package) -> Self {
|
||||
Self {
|
||||
version: value.version(),
|
||||
sha: value.git_sha().map(GitOid::as_tiny_str),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LockEventVersion<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match (self.version, self.sha) {
|
||||
(Some(version), Some(sha)) => write!(f, "v{version} ({sha})"),
|
||||
(Some(version), None) => write!(f, "v{version}"),
|
||||
(None, Some(sha)) => write!(f, "(dynamic) ({sha})"),
|
||||
(None, None) => write!(f, "(dynamic)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A modification to a lockfile.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum LockEvent<'lock> {
|
||||
enum LockEvent<'lock> {
|
||||
Update(
|
||||
DryRun,
|
||||
PackageName,
|
||||
BTreeSet<Option<&'lock Version>>,
|
||||
BTreeSet<Option<&'lock Version>>,
|
||||
BTreeSet<LockEventVersion<'lock>>,
|
||||
BTreeSet<LockEventVersion<'lock>>,
|
||||
),
|
||||
Add(DryRun, PackageName, BTreeSet<Option<&'lock Version>>),
|
||||
Remove(DryRun, PackageName, BTreeSet<Option<&'lock Version>>),
|
||||
Add(DryRun, PackageName, BTreeSet<LockEventVersion<'lock>>),
|
||||
Remove(DryRun, PackageName, BTreeSet<LockEventVersion<'lock>>),
|
||||
}
|
||||
|
||||
impl<'lock> LockEvent<'lock> {
|
||||
/// Detect the change events between an (optional) existing and updated lockfile.
|
||||
pub(crate) fn detect_changes(
|
||||
fn detect_changes(
|
||||
existing_lock: Option<&'lock Lock>,
|
||||
new_lock: &'lock Lock,
|
||||
dry_run: DryRun,
|
||||
) -> impl Iterator<Item = Self> {
|
||||
// Identify the package-versions in the existing lockfile.
|
||||
let mut existing_packages: FxHashMap<&PackageName, BTreeSet<Option<&Version>>> =
|
||||
let mut existing_packages: FxHashMap<&PackageName, BTreeSet<LockEventVersion>> =
|
||||
if let Some(existing_lock) = existing_lock {
|
||||
existing_lock.packages().iter().fold(
|
||||
FxHashMap::with_capacity_and_hasher(
|
||||
@@ -1386,7 +1415,7 @@ impl<'lock> LockEvent<'lock> {
|
||||
|mut acc, package| {
|
||||
acc.entry(package.name())
|
||||
.or_default()
|
||||
.insert(package.version());
|
||||
.insert(LockEventVersion::from(package));
|
||||
acc
|
||||
},
|
||||
)
|
||||
@@ -1395,13 +1424,13 @@ impl<'lock> LockEvent<'lock> {
|
||||
};
|
||||
|
||||
// Identify the package-versions in the updated lockfile.
|
||||
let mut new_packages: FxHashMap<&PackageName, BTreeSet<Option<&Version>>> =
|
||||
let mut new_packages: FxHashMap<&PackageName, BTreeSet<LockEventVersion>> =
|
||||
new_lock.packages().iter().fold(
|
||||
FxHashMap::with_capacity_and_hasher(new_lock.packages().len(), FxBuildHasher),
|
||||
|mut acc, package| {
|
||||
acc.entry(package.name())
|
||||
.or_default()
|
||||
.insert(package.version());
|
||||
.insert(LockEventVersion::from(package));
|
||||
acc
|
||||
},
|
||||
);
|
||||
@@ -1435,23 +1464,16 @@ impl<'lock> LockEvent<'lock> {
|
||||
|
||||
impl std::fmt::Display for LockEvent<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// Format a version for inclusion in the upgrade report.
|
||||
fn format_version(version: Option<&Version>) -> String {
|
||||
version
|
||||
.map(|version| format!("v{version}"))
|
||||
.unwrap_or_else(|| "(dynamic)".to_string())
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Update(dry_run, name, existing_versions, new_versions) => {
|
||||
let existing_versions = existing_versions
|
||||
.iter()
|
||||
.map(|version| format_version(*version))
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let new_versions = new_versions
|
||||
.iter()
|
||||
.map(|version| format_version(*version))
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
@@ -1470,7 +1492,7 @@ impl std::fmt::Display for LockEvent<'_> {
|
||||
Self::Add(dry_run, name, new_versions) => {
|
||||
let new_versions = new_versions
|
||||
.iter()
|
||||
.map(|version| format_version(*version))
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
@@ -1485,7 +1507,7 @@ impl std::fmt::Display for LockEvent<'_> {
|
||||
Self::Remove(dry_run, name, existing_versions) => {
|
||||
let existing_versions = existing_versions
|
||||
.iter()
|
||||
.map(|version| format_version(*version))
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
|
||||
+115
-6
@@ -407,14 +407,15 @@ fn lock_sdist_git() -> Result<()> {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###);
|
||||
Updated uv-public-pypackage v0.1.0 (0dacfd66) -> v0.1.0 (b270df1a)
|
||||
");
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
||||
@@ -738,14 +739,15 @@ fn lock_sdist_git_pep508() -> Result<()> {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###);
|
||||
Updated uv-public-pypackage v0.1.0 (0dacfd66) -> v0.1.0 (b270df1a)
|
||||
");
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
||||
@@ -4942,6 +4944,7 @@ fn lock_git_sha() -> Result<()> {
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Updated uv-public-pypackage v0.1.0 (0dacfd66) -> v0.1.0 (b270df1a)
|
||||
");
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
@@ -12937,14 +12940,15 @@ fn lock_mismatched_sources() -> Result<()> {
|
||||
});
|
||||
|
||||
// If we run with `--no-sources`, we should use the URL provided in `project.dependencies`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--no-sources"), @r###"
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--no-sources"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
"###);
|
||||
Updated uv-public-pypackage v0.1.0 (0dacfd66) -> v0.1.0 (b270df1a)
|
||||
");
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
||||
@@ -31519,6 +31523,111 @@ fn lock_android() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// See: <https://github.com/astral-sh/uv/issues/9832#issuecomment-2539121761>
|
||||
#[test]
|
||||
fn lock_git_change_log() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12.0"
|
||||
dependencies = [
|
||||
"typing-extensions",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
typing-extensions = { git = "https://github.com/python/typing_extensions" }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Write a stale commit.
|
||||
context.temp_dir.child("uv.lock").write_str(
|
||||
r#"
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12.0"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[[package]]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "typing-extensions", git = "https://github.com/python/typing_extensions?rev=4f42e6bf0052129bc6dae5e71699a409652d2091" }]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { git = "https://github.com/python/typing_extensions?rev=4f42e6bf0052129bc6dae5e71699a409652d2091#4f42e6bf0052129bc6dae5e71699a409652d2091" }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--dry-run"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Update typing-extensions v4.15.0 (4f42e6bf) -> v4.15.0 (9215c953)
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Updated typing-extensions v4.15.0 (4f42e6bf) -> v4.15.0 (9215c953)
|
||||
");
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12.[X]"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[[package]]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "typing-extensions", git = "https://github.com/python/typing_extensions" }]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { git = "https://github.com/python/typing_extensions#9215c953610ca4e4ce7ae840a0a804505da70a05" }
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_required_intersection() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
Reference in New Issue
Block a user