11use crate :: core:: registry:: PackageRegistry ;
22use crate :: core:: resolver:: features:: { CliFeatures , HasDevUnits } ;
3+ use crate :: core:: shell:: Verbosity ;
4+ use crate :: core:: Registry as _;
35use crate :: core:: { PackageId , PackageIdSpec , PackageIdSpecQuery } ;
46use crate :: core:: { Resolve , SourceId , Workspace } ;
57use crate :: ops;
8+ use crate :: sources:: source:: QueryKind ;
69use crate :: util:: cache_lock:: CacheLockMode ;
710use crate :: util:: config:: Config ;
811use crate :: util:: style;
@@ -161,36 +164,137 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
161164 let print_change = |status : & str , msg : String , color : & Style | {
162165 opts. config . shell ( ) . status_with_color ( status, msg, color)
163166 } ;
164- for ( removed, added) in compare_dependency_graphs ( & previous_resolve, & resolve) {
167+ let mut unchanged_behind = 0 ;
168+ for ResolvedPackageVersions {
169+ removed,
170+ added,
171+ unchanged,
172+ } in compare_dependency_graphs ( & previous_resolve, & resolve)
173+ {
174+ fn format_latest ( version : semver:: Version ) -> String {
175+ let warn = style:: WARN ;
176+ format ! ( " {warn}(latest: v{version}){warn:#}" )
177+ }
178+ fn is_latest ( candidate : & semver:: Version , current : & semver:: Version ) -> bool {
179+ current < candidate
180+ // Only match pre-release if major.minor.patch are the same
181+ && ( candidate. pre . is_empty ( )
182+ || ( candidate. major == current. major
183+ && candidate. minor == current. minor
184+ && candidate. patch == current. patch ) )
185+ }
186+ let possibilities = if let Some ( query) = [ added. iter ( ) , unchanged. iter ( ) ]
187+ . into_iter ( )
188+ . flatten ( )
189+ . next ( )
190+ . filter ( |s| s. source_id ( ) . is_registry ( ) )
191+ {
192+ let query =
193+ crate :: core:: dependency:: Dependency :: parse ( query. name ( ) , None , query. source_id ( ) ) ?;
194+ loop {
195+ match registry. query_vec ( & query, QueryKind :: Exact ) {
196+ std:: task:: Poll :: Ready ( res) => {
197+ break res?;
198+ }
199+ std:: task:: Poll :: Pending => registry. block_until_ready ( ) ?,
200+ }
201+ }
202+ } else {
203+ vec ! [ ]
204+ } ;
205+
165206 if removed. len ( ) == 1 && added. len ( ) == 1 {
166- let msg = if removed[ 0 ] . source_id ( ) . is_git ( ) {
207+ let added = added. into_iter ( ) . next ( ) . unwrap ( ) ;
208+ let removed = removed. into_iter ( ) . next ( ) . unwrap ( ) ;
209+
210+ let latest = if !possibilities. is_empty ( ) {
211+ possibilities
212+ . iter ( )
213+ . map ( |s| s. as_summary ( ) )
214+ . filter ( |s| is_latest ( s. version ( ) , added. version ( ) ) )
215+ . map ( |s| s. version ( ) . clone ( ) )
216+ . max ( )
217+ . map ( format_latest)
218+ } else {
219+ None
220+ }
221+ . unwrap_or_default ( ) ;
222+
223+ let msg = if removed. source_id ( ) . is_git ( ) {
167224 format ! (
168- "{} -> #{}" ,
169- removed[ 0 ] ,
170- & added[ 0 ] . source_id( ) . precise_git_fragment( ) . unwrap( ) [ ..8 ] ,
225+ "{removed} -> #{}" ,
226+ & added. source_id( ) . precise_git_fragment( ) . unwrap( ) [ ..8 ] ,
171227 )
172228 } else {
173- format ! ( "{} -> v{}" , removed [ 0 ] , added[ 0 ] . version( ) )
229+ format ! ( "{removed } -> v{}{latest} " , added. version( ) )
174230 } ;
175231
176232 // If versions differ only in build metadata, we call it an "update"
177233 // regardless of whether the build metadata has gone up or down.
178234 // This metadata is often stuff like git commit hashes, which are
179235 // not meaningfully ordered.
180- if removed[ 0 ] . version ( ) . cmp_precedence ( added[ 0 ] . version ( ) ) == Ordering :: Greater {
236+ if removed. version ( ) . cmp_precedence ( added. version ( ) ) == Ordering :: Greater {
181237 print_change ( "Downgrading" , msg, & style:: WARN ) ?;
182238 } else {
183239 print_change ( "Updating" , msg, & style:: GOOD ) ?;
184240 }
185241 } else {
186242 for package in removed. iter ( ) {
187- print_change ( "Removing" , format ! ( "{}" , package ) , & style:: ERROR ) ?;
243+ print_change ( "Removing" , format ! ( "{package}" ) , & style:: ERROR ) ?;
188244 }
189245 for package in added. iter ( ) {
190- print_change ( "Adding" , format ! ( "{}" , package) , & style:: NOTE ) ?;
246+ let latest = if !possibilities. is_empty ( ) {
247+ possibilities
248+ . iter ( )
249+ . map ( |s| s. as_summary ( ) )
250+ . filter ( |s| is_latest ( s. version ( ) , package. version ( ) ) )
251+ . map ( |s| s. version ( ) . clone ( ) )
252+ . max ( )
253+ . map ( format_latest)
254+ } else {
255+ None
256+ }
257+ . unwrap_or_default ( ) ;
258+
259+ print_change ( "Adding" , format ! ( "{package}{latest}" ) , & style:: NOTE ) ?;
260+ }
261+ }
262+ for package in & unchanged {
263+ let latest = if !possibilities. is_empty ( ) {
264+ possibilities
265+ . iter ( )
266+ . map ( |s| s. as_summary ( ) )
267+ . filter ( |s| is_latest ( s. version ( ) , package. version ( ) ) )
268+ . map ( |s| s. version ( ) . clone ( ) )
269+ . max ( )
270+ . map ( format_latest)
271+ } else {
272+ None
273+ } ;
274+
275+ if let Some ( latest) = latest {
276+ unchanged_behind += 1 ;
277+ if opts. config . shell ( ) . verbosity ( ) == Verbosity :: Verbose {
278+ opts. config . shell ( ) . status_with_color (
279+ "Unchanged" ,
280+ format ! ( "{package}{latest}" ) ,
281+ & anstyle:: Style :: new ( ) . bold ( ) ,
282+ ) ?;
283+ }
191284 }
192285 }
193286 }
287+ if opts. config . shell ( ) . verbosity ( ) == Verbosity :: Verbose {
288+ opts. config . shell ( ) . note (
289+ "to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`" ,
290+ ) ?;
291+ } else {
292+ if 0 < unchanged_behind {
293+ opts. config . shell ( ) . note ( format ! (
294+ "pass `--verbose` to see {unchanged_behind} unchanged dependencies behind latest"
295+ ) ) ?;
296+ }
297+ }
194298 if opts. dry_run {
195299 opts. config
196300 . shell ( )
@@ -215,73 +319,87 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
215319 }
216320 }
217321
322+ #[ derive( Default , Clone , Debug ) ]
323+ struct ResolvedPackageVersions {
324+ removed : Vec < PackageId > ,
325+ added : Vec < PackageId > ,
326+ unchanged : Vec < PackageId > ,
327+ }
218328 fn compare_dependency_graphs (
219329 previous_resolve : & Resolve ,
220330 resolve : & Resolve ,
221- ) -> Vec < ( Vec < PackageId > , Vec < PackageId > ) > {
331+ ) -> Vec < ResolvedPackageVersions > {
222332 fn key ( dep : PackageId ) -> ( & ' static str , SourceId ) {
223333 ( dep. name ( ) . as_str ( ) , dep. source_id ( ) )
224334 }
225335
226- // Removes all package IDs in `b` from `a`. Note that this is somewhat
227- // more complicated because the equality for source IDs does not take
228- // precise versions into account (e.g., git shas), but we want to take
229- // that into account here.
230- fn vec_subtract ( a : & [ PackageId ] , b : & [ PackageId ] ) -> Vec < PackageId > {
231- a. iter ( )
232- . filter ( |a| {
233- // If this package ID is not found in `b`, then it's definitely
234- // in the subtracted set.
235- let Ok ( i) = b. binary_search ( a) else {
236- return true ;
237- } ;
336+ fn vec_subset ( a : & [ PackageId ] , b : & [ PackageId ] ) -> Vec < PackageId > {
337+ a. iter ( ) . filter ( |a| !contains_id ( b, a) ) . cloned ( ) . collect ( )
338+ }
238339
239- // If we've found `a` in `b`, then we iterate over all instances
240- // (we know `b` is sorted) and see if they all have different
241- // precise versions. If so, then `a` isn't actually in `b` so
242- // we'll let it through.
243- //
244- // Note that we only check this for non-registry sources,
245- // however, as registries contain enough version information in
246- // the package ID to disambiguate.
247- if a. source_id ( ) . is_registry ( ) {
248- return false ;
249- }
250- b[ i..]
251- . iter ( )
252- . take_while ( |b| a == b)
253- . all ( |b| !a. source_id ( ) . has_same_precise_as ( b. source_id ( ) ) )
254- } )
255- . cloned ( )
256- . collect ( )
340+ fn vec_intersection ( a : & [ PackageId ] , b : & [ PackageId ] ) -> Vec < PackageId > {
341+ a. iter ( ) . filter ( |a| contains_id ( b, a) ) . cloned ( ) . collect ( )
342+ }
343+
344+ // Check if a PackageId is present `b` from `a`.
345+ //
346+ // Note that this is somewhat more complicated because the equality for source IDs does not
347+ // take precise versions into account (e.g., git shas), but we want to take that into
348+ // account here.
349+ fn contains_id ( haystack : & [ PackageId ] , needle : & PackageId ) -> bool {
350+ let Ok ( i) = haystack. binary_search ( needle) else {
351+ return false ;
352+ } ;
353+
354+ // If we've found `a` in `b`, then we iterate over all instances
355+ // (we know `b` is sorted) and see if they all have different
356+ // precise versions. If so, then `a` isn't actually in `b` so
357+ // we'll let it through.
358+ //
359+ // Note that we only check this for non-registry sources,
360+ // however, as registries contain enough version information in
361+ // the package ID to disambiguate.
362+ if needle. source_id ( ) . is_registry ( ) {
363+ return true ;
364+ }
365+ haystack[ i..]
366+ . iter ( )
367+ . take_while ( |b| & needle == b)
368+ . any ( |b| needle. source_id ( ) . has_same_precise_as ( b. source_id ( ) ) )
257369 }
258370
259371 // Map `(package name, package source)` to `(removed versions, added versions)`.
260372 let mut changes = BTreeMap :: new ( ) ;
261- let empty = ( Vec :: new ( ) , Vec :: new ( ) ) ;
373+ let empty = ResolvedPackageVersions :: default ( ) ;
262374 for dep in previous_resolve. iter ( ) {
263375 changes
264376 . entry ( key ( dep) )
265377 . or_insert_with ( || empty. clone ( ) )
266- . 0
378+ . removed
267379 . push ( dep) ;
268380 }
269381 for dep in resolve. iter ( ) {
270382 changes
271383 . entry ( key ( dep) )
272384 . or_insert_with ( || empty. clone ( ) )
273- . 1
385+ . added
274386 . push ( dep) ;
275387 }
276388
277389 for v in changes. values_mut ( ) {
278- let ( ref mut old, ref mut new) = * v;
390+ let ResolvedPackageVersions {
391+ removed : ref mut old,
392+ added : ref mut new,
393+ unchanged : ref mut other,
394+ } = * v;
279395 old. sort ( ) ;
280396 new. sort ( ) ;
281- let removed = vec_subtract ( old, new) ;
282- let added = vec_subtract ( new, old) ;
397+ let removed = vec_subset ( old, new) ;
398+ let added = vec_subset ( new, old) ;
399+ let unchanged = vec_intersection ( new, old) ;
283400 * old = removed;
284401 * new = added;
402+ * other = unchanged;
285403 }
286404 debug ! ( "{:#?}" , changes) ;
287405
0 commit comments