1
1
use crate :: core:: registry:: PackageRegistry ;
2
2
use crate :: core:: resolver:: features:: { CliFeatures , HasDevUnits } ;
3
+ use crate :: core:: shell:: Verbosity ;
4
+ use crate :: core:: Registry as _;
3
5
use crate :: core:: { PackageId , PackageIdSpec , PackageIdSpecQuery } ;
4
6
use crate :: core:: { Resolve , SourceId , Workspace } ;
5
7
use crate :: ops;
8
+ use crate :: sources:: source:: QueryKind ;
6
9
use crate :: util:: cache_lock:: CacheLockMode ;
7
10
use crate :: util:: config:: Config ;
8
11
use crate :: util:: style;
@@ -161,36 +164,137 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
161
164
let print_change = |status : & str , msg : String , color : & Style | {
162
165
opts. config . shell ( ) . status_with_color ( status, msg, color)
163
166
} ;
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
+
165
206
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 ( ) {
167
224
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 ] ,
171
227
)
172
228
} else {
173
- format ! ( "{} -> v{}" , removed [ 0 ] , added[ 0 ] . version( ) )
229
+ format ! ( "{removed } -> v{}{latest} " , added. version( ) )
174
230
} ;
175
231
176
232
// If versions differ only in build metadata, we call it an "update"
177
233
// regardless of whether the build metadata has gone up or down.
178
234
// This metadata is often stuff like git commit hashes, which are
179
235
// 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 {
181
237
print_change ( "Downgrading" , msg, & style:: WARN ) ?;
182
238
} else {
183
239
print_change ( "Updating" , msg, & style:: GOOD ) ?;
184
240
}
185
241
} else {
186
242
for package in removed. iter ( ) {
187
- print_change ( "Removing" , format ! ( "{}" , package ) , & style:: ERROR ) ?;
243
+ print_change ( "Removing" , format ! ( "{package}" ) , & style:: ERROR ) ?;
188
244
}
189
245
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
+ }
191
284
}
192
285
}
193
286
}
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
+ }
194
298
if opts. dry_run {
195
299
opts. config
196
300
. shell ( )
@@ -215,73 +319,87 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
215
319
}
216
320
}
217
321
322
+ #[ derive( Default , Clone , Debug ) ]
323
+ struct ResolvedPackageVersions {
324
+ removed : Vec < PackageId > ,
325
+ added : Vec < PackageId > ,
326
+ unchanged : Vec < PackageId > ,
327
+ }
218
328
fn compare_dependency_graphs (
219
329
previous_resolve : & Resolve ,
220
330
resolve : & Resolve ,
221
- ) -> Vec < ( Vec < PackageId > , Vec < PackageId > ) > {
331
+ ) -> Vec < ResolvedPackageVersions > {
222
332
fn key ( dep : PackageId ) -> ( & ' static str , SourceId ) {
223
333
( dep. name ( ) . as_str ( ) , dep. source_id ( ) )
224
334
}
225
335
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
+ }
238
339
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 ( ) ) )
257
369
}
258
370
259
371
// Map `(package name, package source)` to `(removed versions, added versions)`.
260
372
let mut changes = BTreeMap :: new ( ) ;
261
- let empty = ( Vec :: new ( ) , Vec :: new ( ) ) ;
373
+ let empty = ResolvedPackageVersions :: default ( ) ;
262
374
for dep in previous_resolve. iter ( ) {
263
375
changes
264
376
. entry ( key ( dep) )
265
377
. or_insert_with ( || empty. clone ( ) )
266
- . 0
378
+ . removed
267
379
. push ( dep) ;
268
380
}
269
381
for dep in resolve. iter ( ) {
270
382
changes
271
383
. entry ( key ( dep) )
272
384
. or_insert_with ( || empty. clone ( ) )
273
- . 1
385
+ . added
274
386
. push ( dep) ;
275
387
}
276
388
277
389
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;
279
395
old. sort ( ) ;
280
396
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) ;
283
400
* old = removed;
284
401
* new = added;
402
+ * other = unchanged;
285
403
}
286
404
debug ! ( "{:#?}" , changes) ;
287
405
0 commit comments