From 9f51dca37caf91e20eb0a099f605ae183a66269e Mon Sep 17 00:00:00 2001 From: "Tomasz (Tom) Kramkowski" Date: Mon, 8 Dec 2025 22:42:47 +0000 Subject: [PATCH 1/9] Fix VersionOrUrlRef::url lifetimes --- crates/uv-distribution-types/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index c0a6cd2bdddc1..cbe7668ec993d 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -122,9 +122,9 @@ pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> { Url(&'a T), } -impl VersionOrUrlRef<'_, T> { +impl<'a, T: Pep508Url> VersionOrUrlRef<'a, T> { /// If it is a URL, return its value. - pub fn url(&self) -> Option<&T> { + pub fn url(&self) -> Option<&'a T> { match self { Self::Version(_) => None, Self::Url(url) => Some(url), From fc5d5e871cfabe1c9d0e40a1985a641fbffae409 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Nov 2025 23:10:27 -0500 Subject: [PATCH 2/9] Add initial work --- crates/uv/src/commands/pip/operations.rs | 15 +++ crates/uv/src/commands/project/remove.rs | 2 +- crates/uv/src/commands/project/run.rs | 4 +- crates/uv/src/commands/project/sync.rs | 131 +++++++++++++++++----- crates/uv/src/commands/project/version.rs | 2 +- crates/uv/tests/it/sync.rs | 84 ++++++++++++-- 6 files changed, 200 insertions(+), 38 deletions(-) diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 1886dec3cf974..fbc79bd0b3654 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -34,6 +34,7 @@ use uv_platform_tags::Tags; use uv_preview::Preview; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; use uv_python::{PythonEnvironment, PythonInstallation}; +use uv_redacted::DisplaySafeUrl; use uv_requirements::{ GroupsSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification, SourceTree, SourceTreeResolver, @@ -454,6 +455,20 @@ impl ChangedDist { }, } } + + pub(crate) fn version(&self) -> Option<&Version> { + match self { + Self::Local(dist) => Some(dist.installed_version().version()), + Self::Remote(dist) => dist.version(), + } + } + + pub(crate) fn url(&self) -> Option<&DisplaySafeUrl> { + match self { + Self::Local(dist) => dist.installed_version().url(), + Self::Remote(dist) => dist.version_or_url().url().map(|url| &**url), + } + } } /// A summary of the changes made to the environment during an installation. diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 6487c5484843d..6b79cd7105f09 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -375,7 +375,7 @@ pub(crate) async fn remove( ) .await { - Ok(()) => {} + Ok(_) => {} Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls()) .report(err) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index dc8b201d0f6e8..7e912c2d0bb33 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -339,7 +339,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl ) .await { - Ok(()) => {} + Ok(_) => {} Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls( client_builder.is_native_tls(), @@ -867,7 +867,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl ) .await { - Ok(()) => {} + Ok(_) => {} Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls( client_builder.is_native_tls(), diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 2542965e8cd53..98b2ce7c736c6 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -19,7 +19,7 @@ use uv_configuration::{ use uv_dispatch::BuildDispatch; use uv_distribution::LoweredExtraBuildDependencies; use uv_distribution_types::{ - DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist, + DirectorySourceDist, Dist, Index, Name, Requirement, Resolution, ResolvedDist, SourceDist, }; use uv_fs::{PortablePathBuf, Simplified}; use uv_installer::{InstallationStrategy, SitePackages}; @@ -37,16 +37,16 @@ use uv_workspace::pyproject::Source; use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache}; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger}; -use crate::commands::pip::operations::Modifications; +use crate::commands::pip::operations::{ChangedDist, Changelog, Modifications}; use crate::commands::pip::resolution_markers; use crate::commands::pip::{operations, resolution_tags}; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::{LockMode, LockOperation, LockResult}; use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - PlatformState, ProjectEnvironment, ProjectError, ScriptEnvironment, UniversalState, - default_dependency_groups, detect_conflicts, script_extra_build_requires, script_specification, - update_environment, + EnvironmentUpdate, PlatformState, ProjectEnvironment, ProjectError, ScriptEnvironment, + UniversalState, default_dependency_groups, detect_conflicts, script_extra_build_requires, + script_specification, update_environment, }; use crate::commands::{ExitStatus, diagnostics}; use crate::printer::Printer; @@ -207,11 +207,12 @@ pub(crate) async fn sync( }) .ok(); - let sync_report = SyncReport { + let mut sync_report = SyncReport { dry_run: dry_run.enabled(), environment: EnvironmentReport::from(&environment), action: SyncAction::from(&environment), target: TargetName::from(&target), + packages: Vec::new(), }; // Show the intermediate results if relevant @@ -292,7 +293,8 @@ pub(crate) async fn sync( ) .await { - Ok(..) => { + Ok(EnvironmentUpdate { changelog, .. }) => { + sync_report.packages = PackageChangeReport::from_changelog(&changelog); // Generate a report for the script without a lockfile let report = Report { schema: SchemaReport::default(), @@ -387,27 +389,13 @@ pub(crate) async fn sync( writeln!(printer.stderr(), "{message}")?; } - let report = Report { - schema: SchemaReport::default(), - target: TargetName::from(&target), - project: target.project().map(ProjectReport::from), - script: target.script().map(ScriptReport::from), - sync: sync_report, - lock: Some(lock_report), - dry_run: dry_run.enabled(), - }; - - if let Some(output) = report.format(output_format) { - writeln!(printer.stdout_important(), "{output}")?; - } - // Identify the installation target. let sync_target = identify_installation_target(&target, outcome.lock(), all_packages, &package); let state = state.fork(); // Perform the sync operation. - match do_sync( + let changelog = match do_sync( sync_target, &environment, &extras, @@ -430,13 +418,29 @@ pub(crate) async fn sync( ) .await { - Ok(()) => {} + Ok(changelog) => changelog, Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls()) .report(err) .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); } Err(err) => return Err(err.into()), + }; + + sync_report.packages = PackageChangeReport::from_changelog(&changelog); + + let report = Report { + schema: SchemaReport::default(), + target: TargetName::from(&target), + project: target.project().map(ProjectReport::from), + script: target.script().map(ScriptReport::from), + sync: sync_report, + lock: Some(lock_report), + dry_run: dry_run.enabled(), + }; + + if let Some(output) = report.format(output_format) { + writeln!(printer.stdout_important(), "{output}")?; } match outcome { @@ -614,7 +618,7 @@ pub(super) async fn do_sync( dry_run: DryRun, printer: Printer, preview: Preview, -) -> Result<(), ProjectError> { +) -> Result { // Extract the project settings. let InstallerSettingsRef { index_locations, @@ -825,7 +829,7 @@ pub(super) async fn do_sync( let site_packages = SitePackages::from_environment(venv)?; // Sync the environment. - operations::install( + let changelog = operations::install( &resolution, site_packages, InstallationStrategy::Strict, @@ -850,7 +854,7 @@ pub(super) async fn do_sync( ) .await?; - Ok(()) + Ok(changelog) } /// Filter out any virtual workspace members. @@ -1254,6 +1258,9 @@ struct SyncReport { environment: EnvironmentReport, /// The action performed during the sync, e.g., what was done to the environment. action: SyncAction, + /// The packages that changed during the sync. + #[serde(default)] + packages: Vec, // We store these fields so the report can format itself self-contained, but the outer // [`Report`] is intended to include these in user-facing output @@ -1276,6 +1283,7 @@ impl SyncReport { let Self { environment, action, + packages: _, dry_run, target, } = self; @@ -1294,6 +1302,77 @@ impl SyncReport { } } +/// A summary of a single package change performed during sync. +#[derive(Serialize, Debug, Clone)] +struct PackageChangeReport { + /// The normalized package name. + name: String, + /// The resolved version of the package. + #[serde(skip_serializing_if = "Option::is_none")] + version: Option, + /// The source for URL-based requirements. + #[serde(skip_serializing_if = "Option::is_none")] + source: Option, + /// The action that was taken for the package. + action: PackageChangeAction, +} + +impl PackageChangeReport { + fn from_changelog(changelog: &Changelog) -> Vec { + let mut changes: Vec<_> = changelog + .uninstalled + .iter() + .map(|dist| Self::from_dist(dist, PackageChangeAction::Removed)) + .chain( + changelog + .installed + .iter() + .map(|dist| Self::from_dist(dist, PackageChangeAction::Added)), + ) + .chain( + changelog + .reinstalled + .iter() + .map(|dist| Self::from_dist(dist, PackageChangeAction::Reinstalled)), + ) + .collect(); + + changes.sort_by(|a, b| { + a.name + .cmp(&b.name) + .then_with(|| a.action.cmp(&b.action)) + .then_with(|| a.version.cmp(&b.version)) + }); + changes + } + + fn from_dist(dist: &ChangedDist, action: PackageChangeAction) -> Self { + Self { + name: dist.name().to_string(), + version: dist.version().cloned(), + source: dist.url().map(|url| PackageChangeSourceReport { + url: url.to_string(), + }), + action, + } + } +} + +/// The action taken on an individual package during sync. +#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "snake_case")] +enum PackageChangeAction { + Removed, + Added, + Reinstalled, +} + +/// The source for a package change, when it originated from a URL requirement. +#[derive(Serialize, Debug, Clone)] +struct PackageChangeSourceReport { + url: String, +} + /// The report for a lock operation. #[derive(Debug, Serialize)] struct LockReport { diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index fd81992a2d66e..7de9f78308cb2 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -680,7 +680,7 @@ async fn lock_and_sync( ) .await { - Ok(()) => {} + Ok(_) => {} Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls()) .report(err) diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 35df3d50be8c9..6a70d07c689d5 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -420,7 +420,14 @@ fn sync_json() -> Result<()> { "implementation": "cpython" } }, - "action": "check" + "action": "check", + "packages": [ + { + "name": "iniconfig", + "version": "2.0.0", + "action": "added" + } + ] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -464,7 +471,8 @@ fn sync_json() -> Result<()> { "implementation": "cpython" } }, - "action": "check" + "action": "check", + "packages": [] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -503,7 +511,8 @@ fn sync_json() -> Result<()> { "implementation": "cpython" } }, - "action": "check" + "action": "check", + "packages": [] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -569,7 +578,8 @@ fn sync_json() -> Result<()> { "implementation": "cpython" } }, - "action": "check" + "action": "check", + "packages": [] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -629,7 +639,14 @@ fn sync_dry_json() -> Result<()> { "implementation": "cpython" } }, - "action": "create" + "action": "create", + "packages": [ + { + "name": "iniconfig", + "version": "2.0.0", + "action": "added" + } + ] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -6856,7 +6873,24 @@ fn sync_active_script_environment_json() -> Result<()> { "implementation": "cpython" } }, - "action": "create" + "action": "create", + "packages": [ + { + "name": "anyio", + "version": "4.3.0", + "action": "added" + }, + { + "name": "idna", + "version": "3.6", + "action": "added" + }, + { + "name": "sniffio", + "version": "1.3.1", + "action": "added" + } + ] }, "lock": null, "dry_run": false @@ -6902,7 +6936,24 @@ fn sync_active_script_environment_json() -> Result<()> { "implementation": "cpython" } }, - "action": "create" + "action": "create", + "packages": [ + { + "name": "anyio", + "version": "4.3.0", + "action": "added" + }, + { + "name": "idna", + "version": "3.6", + "action": "added" + }, + { + "name": "sniffio", + "version": "1.3.1", + "action": "added" + } + ] }, "lock": null, "dry_run": false @@ -6961,7 +7012,24 @@ fn sync_active_script_environment_json() -> Result<()> { "implementation": "cpython" } }, - "action": "update" + "action": "update", + "packages": [ + { + "name": "anyio", + "version": "4.3.0", + "action": "added" + }, + { + "name": "idna", + "version": "3.6", + "action": "added" + }, + { + "name": "sniffio", + "version": "1.3.1", + "action": "added" + } + ] }, "lock": null, "dry_run": false From a4592ed3a3fca471ca16491c2ee4d41c4e5e0c2d Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Nov 2025 23:55:24 -0500 Subject: [PATCH 3/9] Inline `SyncReport` construction --- crates/uv/src/commands/project/sync.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 98b2ce7c736c6..8f0a59fae49cd 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -294,14 +294,16 @@ pub(crate) async fn sync( .await { Ok(EnvironmentUpdate { changelog, .. }) => { - sync_report.packages = PackageChangeReport::from_changelog(&changelog); // Generate a report for the script without a lockfile let report = Report { schema: SchemaReport::default(), target: TargetName::from(&target), project: None, script: Some(ScriptReport::from(script)), - sync: sync_report, + sync: SyncReport { + packages: PackageChangeReport::from_changelog(&changelog), + ..sync_report + }, lock: None, dry_run: dry_run.enabled(), }; @@ -427,14 +429,15 @@ pub(crate) async fn sync( Err(err) => return Err(err.into()), }; - sync_report.packages = PackageChangeReport::from_changelog(&changelog); - let report = Report { schema: SchemaReport::default(), target: TargetName::from(&target), project: target.project().map(ProjectReport::from), script: target.script().map(ScriptReport::from), - sync: sync_report, + sync: SyncReport { + packages: PackageChangeReport::from_changelog(&changelog), + ..sync_report + }, lock: Some(lock_report), dry_run: dry_run.enabled(), }; From 9019fea16c46e9e1d8df2a5b7cb6c8a5ea33d757 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 9 Nov 2025 23:57:13 -0500 Subject: [PATCH 4/9] Remove superfluous `mut` --- crates/uv/src/commands/project/sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 8f0a59fae49cd..233f332591661 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -207,7 +207,7 @@ pub(crate) async fn sync( }) .ok(); - let mut sync_report = SyncReport { + let sync_report = SyncReport { dry_run: dry_run.enabled(), environment: EnvironmentReport::from(&environment), action: SyncAction::from(&environment), From cc91e47595d00fe4d9def797c65bdeed9b612d44 Mon Sep 17 00:00:00 2001 From: "Tomasz (Tom) Kramkowski" Date: Thu, 4 Dec 2025 11:21:06 +0000 Subject: [PATCH 5/9] Don't eagerly stringify --- crates/uv/src/commands/project/sync.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 233f332591661..3bf80bcfa8790 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -28,6 +28,7 @@ use uv_pep508::{MarkerTree, VersionOrUrl}; use uv_preview::{Preview, PreviewFeatures}; use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl}; use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; +use uv_redacted::DisplaySafeUrl; use uv_resolver::{FlatIndex, ForkStrategy, Installable, Lock, PrereleaseMode, ResolutionMode}; use uv_scripts::Pep723Script; use uv_settings::PythonInstallMirrors; @@ -1309,7 +1310,7 @@ impl SyncReport { #[derive(Serialize, Debug, Clone)] struct PackageChangeReport { /// The normalized package name. - name: String, + name: PackageName, /// The resolved version of the package. #[serde(skip_serializing_if = "Option::is_none")] version: Option, @@ -1351,11 +1352,11 @@ impl PackageChangeReport { fn from_dist(dist: &ChangedDist, action: PackageChangeAction) -> Self { Self { - name: dist.name().to_string(), + name: dist.name().clone(), version: dist.version().cloned(), - source: dist.url().map(|url| PackageChangeSourceReport { - url: url.to_string(), - }), + source: dist + .url() + .map(|url| PackageChangeSourceReport { url: url.clone() }), action, } } @@ -1373,7 +1374,7 @@ enum PackageChangeAction { /// The source for a package change, when it originated from a URL requirement. #[derive(Serialize, Debug, Clone)] struct PackageChangeSourceReport { - url: String, + url: DisplaySafeUrl, } /// The report for a lock operation. From f026d1748aa4be6c13eab7c65754bf78da7b6953 Mon Sep 17 00:00:00 2001 From: "Tomasz (Tom) Kramkowski" Date: Fri, 12 Dec 2025 12:12:15 +0000 Subject: [PATCH 6/9] Rename `packages` to `changes` --- crates/uv/src/commands/project/sync.rs | 10 +++++----- crates/uv/tests/it/sync.rs | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 3bf80bcfa8790..13f9da3ec5cb3 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -213,7 +213,7 @@ pub(crate) async fn sync( environment: EnvironmentReport::from(&environment), action: SyncAction::from(&environment), target: TargetName::from(&target), - packages: Vec::new(), + changes: Vec::new(), }; // Show the intermediate results if relevant @@ -302,7 +302,7 @@ pub(crate) async fn sync( project: None, script: Some(ScriptReport::from(script)), sync: SyncReport { - packages: PackageChangeReport::from_changelog(&changelog), + changes: PackageChangeReport::from_changelog(&changelog), ..sync_report }, lock: None, @@ -436,7 +436,7 @@ pub(crate) async fn sync( project: target.project().map(ProjectReport::from), script: target.script().map(ScriptReport::from), sync: SyncReport { - packages: PackageChangeReport::from_changelog(&changelog), + changes: PackageChangeReport::from_changelog(&changelog), ..sync_report }, lock: Some(lock_report), @@ -1264,7 +1264,7 @@ struct SyncReport { action: SyncAction, /// The packages that changed during the sync. #[serde(default)] - packages: Vec, + changes: Vec, // We store these fields so the report can format itself self-contained, but the outer // [`Report`] is intended to include these in user-facing output @@ -1287,7 +1287,7 @@ impl SyncReport { let Self { environment, action, - packages: _, + changes: _, dry_run, target, } = self; diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 6a70d07c689d5..fc0a52e401331 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -421,7 +421,7 @@ fn sync_json() -> Result<()> { } }, "action": "check", - "packages": [ + "changes": [ { "name": "iniconfig", "version": "2.0.0", @@ -472,7 +472,7 @@ fn sync_json() -> Result<()> { } }, "action": "check", - "packages": [] + "changes": [] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -512,7 +512,7 @@ fn sync_json() -> Result<()> { } }, "action": "check", - "packages": [] + "changes": [] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -579,7 +579,7 @@ fn sync_json() -> Result<()> { } }, "action": "check", - "packages": [] + "changes": [] }, "lock": { "path": "[TEMP_DIR]/uv.lock", @@ -640,7 +640,7 @@ fn sync_dry_json() -> Result<()> { } }, "action": "create", - "packages": [ + "changes": [ { "name": "iniconfig", "version": "2.0.0", @@ -6874,7 +6874,7 @@ fn sync_active_script_environment_json() -> Result<()> { } }, "action": "create", - "packages": [ + "changes": [ { "name": "anyio", "version": "4.3.0", @@ -6937,7 +6937,7 @@ fn sync_active_script_environment_json() -> Result<()> { } }, "action": "create", - "packages": [ + "changes": [ { "name": "anyio", "version": "4.3.0", @@ -7013,7 +7013,7 @@ fn sync_active_script_environment_json() -> Result<()> { } }, "action": "update", - "packages": [ + "changes": [ { "name": "anyio", "version": "4.3.0", From 96952204b030aba9d59a69986fa0b416db903bf8 Mon Sep 17 00:00:00 2001 From: "Tomasz (Tom) Kramkowski" Date: Thu, 18 Dec 2025 13:52:30 +0000 Subject: [PATCH 7/9] Remove URL from the package change reports for now --- crates/uv/src/commands/pip/operations.rs | 8 -------- crates/uv/src/commands/project/sync.rs | 13 ------------- 2 files changed, 21 deletions(-) diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index fbc79bd0b3654..3a84b34f01b28 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -34,7 +34,6 @@ use uv_platform_tags::Tags; use uv_preview::Preview; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; use uv_python::{PythonEnvironment, PythonInstallation}; -use uv_redacted::DisplaySafeUrl; use uv_requirements::{ GroupsSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification, SourceTree, SourceTreeResolver, @@ -462,13 +461,6 @@ impl ChangedDist { Self::Remote(dist) => dist.version(), } } - - pub(crate) fn url(&self) -> Option<&DisplaySafeUrl> { - match self { - Self::Local(dist) => dist.installed_version().url(), - Self::Remote(dist) => dist.version_or_url().url().map(|url| &**url), - } - } } /// A summary of the changes made to the environment during an installation. diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 13f9da3ec5cb3..d5f2689dde188 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -28,7 +28,6 @@ use uv_pep508::{MarkerTree, VersionOrUrl}; use uv_preview::{Preview, PreviewFeatures}; use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl}; use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; -use uv_redacted::DisplaySafeUrl; use uv_resolver::{FlatIndex, ForkStrategy, Installable, Lock, PrereleaseMode, ResolutionMode}; use uv_scripts::Pep723Script; use uv_settings::PythonInstallMirrors; @@ -1314,9 +1313,6 @@ struct PackageChangeReport { /// The resolved version of the package. #[serde(skip_serializing_if = "Option::is_none")] version: Option, - /// The source for URL-based requirements. - #[serde(skip_serializing_if = "Option::is_none")] - source: Option, /// The action that was taken for the package. action: PackageChangeAction, } @@ -1354,9 +1350,6 @@ impl PackageChangeReport { Self { name: dist.name().clone(), version: dist.version().cloned(), - source: dist - .url() - .map(|url| PackageChangeSourceReport { url: url.clone() }), action, } } @@ -1371,12 +1364,6 @@ enum PackageChangeAction { Reinstalled, } -/// The source for a package change, when it originated from a URL requirement. -#[derive(Serialize, Debug, Clone)] -struct PackageChangeSourceReport { - url: DisplaySafeUrl, -} - /// The report for a lock operation. #[derive(Debug, Serialize)] struct LockReport { From 27c270081070567854ee29826c9354e191edb6a9 Mon Sep 17 00:00:00 2001 From: "Tomasz (Tom) Kramkowski" Date: Thu, 18 Dec 2025 13:54:17 +0000 Subject: [PATCH 8/9] Change Added/Removed to Installed/Uninstalled --- crates/uv/src/commands/project/sync.rs | 8 ++++---- crates/uv/tests/it/sync.rs | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index d5f2689dde188..6eac3b179284e 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -1322,12 +1322,12 @@ impl PackageChangeReport { let mut changes: Vec<_> = changelog .uninstalled .iter() - .map(|dist| Self::from_dist(dist, PackageChangeAction::Removed)) + .map(|dist| Self::from_dist(dist, PackageChangeAction::Uninstalled)) .chain( changelog .installed .iter() - .map(|dist| Self::from_dist(dist, PackageChangeAction::Added)), + .map(|dist| Self::from_dist(dist, PackageChangeAction::Installed)), ) .chain( changelog @@ -1359,8 +1359,8 @@ impl PackageChangeReport { #[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] enum PackageChangeAction { - Removed, - Added, + Uninstalled, + Installed, Reinstalled, } diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index fc0a52e401331..82bc1b774d177 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -425,7 +425,7 @@ fn sync_json() -> Result<()> { { "name": "iniconfig", "version": "2.0.0", - "action": "added" + "action": "installed" } ] }, @@ -644,7 +644,7 @@ fn sync_dry_json() -> Result<()> { { "name": "iniconfig", "version": "2.0.0", - "action": "added" + "action": "installed" } ] }, @@ -6878,17 +6878,17 @@ fn sync_active_script_environment_json() -> Result<()> { { "name": "anyio", "version": "4.3.0", - "action": "added" + "action": "installed" }, { "name": "idna", "version": "3.6", - "action": "added" + "action": "installed" }, { "name": "sniffio", "version": "1.3.1", - "action": "added" + "action": "installed" } ] }, @@ -6941,17 +6941,17 @@ fn sync_active_script_environment_json() -> Result<()> { { "name": "anyio", "version": "4.3.0", - "action": "added" + "action": "installed" }, { "name": "idna", "version": "3.6", - "action": "added" + "action": "installed" }, { "name": "sniffio", "version": "1.3.1", - "action": "added" + "action": "installed" } ] }, @@ -7017,17 +7017,17 @@ fn sync_active_script_environment_json() -> Result<()> { { "name": "anyio", "version": "4.3.0", - "action": "added" + "action": "installed" }, { "name": "idna", "version": "3.6", - "action": "added" + "action": "installed" }, { "name": "sniffio", "version": "1.3.1", - "action": "added" + "action": "installed" } ] }, From 0136edcd54ce6c33746e4fde31c31c47982e104c Mon Sep 17 00:00:00 2001 From: "Tomasz (Tom) Kramkowski" Date: Thu, 18 Dec 2025 13:54:44 +0000 Subject: [PATCH 9/9] Create a PackageChangesReport newtype --- crates/uv/src/commands/project/sync.rs | 65 +++++++++++++------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 6eac3b179284e..712f973f555de 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -212,7 +212,7 @@ pub(crate) async fn sync( environment: EnvironmentReport::from(&environment), action: SyncAction::from(&environment), target: TargetName::from(&target), - changes: Vec::new(), + changes: PackageChangesReport::default(), }; // Show the intermediate results if relevant @@ -301,7 +301,7 @@ pub(crate) async fn sync( project: None, script: Some(ScriptReport::from(script)), sync: SyncReport { - changes: PackageChangeReport::from_changelog(&changelog), + changes: PackageChangesReport::from_changelog(&changelog), ..sync_report }, lock: None, @@ -435,7 +435,7 @@ pub(crate) async fn sync( project: target.project().map(ProjectReport::from), script: target.script().map(ScriptReport::from), sync: SyncReport { - changes: PackageChangeReport::from_changelog(&changelog), + changes: PackageChangesReport::from_changelog(&changelog), ..sync_report }, lock: Some(lock_report), @@ -1263,7 +1263,7 @@ struct SyncReport { action: SyncAction, /// The packages that changed during the sync. #[serde(default)] - changes: Vec, + changes: PackageChangesReport, // We store these fields so the report can format itself self-contained, but the outer // [`Report`] is intended to include these in user-facing output @@ -1305,6 +1305,35 @@ impl SyncReport { } } +/// A summary of all package changes performed during sync. +#[derive(Serialize, Debug, Clone, Default)] +struct PackageChangesReport(Vec); + +impl PackageChangesReport { + fn from_changelog(changelog: &Changelog) -> Self { + let mut changes: Vec<_> = + changelog + .uninstalled + .iter() + .map(|dist| PackageChangeReport::from_dist(dist, PackageChangeAction::Uninstalled)) + .chain(changelog.installed.iter().map(|dist| { + PackageChangeReport::from_dist(dist, PackageChangeAction::Installed) + })) + .chain(changelog.reinstalled.iter().map(|dist| { + PackageChangeReport::from_dist(dist, PackageChangeAction::Reinstalled) + })) + .collect(); + + changes.sort_by(|a, b| { + a.name + .cmp(&b.name) + .then_with(|| a.action.cmp(&b.action)) + .then_with(|| a.version.cmp(&b.version)) + }); + Self(changes) + } +} + /// A summary of a single package change performed during sync. #[derive(Serialize, Debug, Clone)] struct PackageChangeReport { @@ -1318,34 +1347,6 @@ struct PackageChangeReport { } impl PackageChangeReport { - fn from_changelog(changelog: &Changelog) -> Vec { - let mut changes: Vec<_> = changelog - .uninstalled - .iter() - .map(|dist| Self::from_dist(dist, PackageChangeAction::Uninstalled)) - .chain( - changelog - .installed - .iter() - .map(|dist| Self::from_dist(dist, PackageChangeAction::Installed)), - ) - .chain( - changelog - .reinstalled - .iter() - .map(|dist| Self::from_dist(dist, PackageChangeAction::Reinstalled)), - ) - .collect(); - - changes.sort_by(|a, b| { - a.name - .cmp(&b.name) - .then_with(|| a.action.cmp(&b.action)) - .then_with(|| a.version.cmp(&b.version)) - }); - changes - } - fn from_dist(dist: &ChangedDist, action: PackageChangeAction) -> Self { Self { name: dist.name().clone(),