diff --git a/src/cargo/ops/cargo_generate_lockfile.rs b/src/cargo/ops/cargo_generate_lockfile.rs index 534a7cdc5da..f561664b876 100644 --- a/src/cargo/ops/cargo_generate_lockfile.rs +++ b/src/cargo/ops/cargo_generate_lockfile.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::path::Path; use core::PackageId; use core::registry::PackageRegistry; -use core::{Source, Resolve}; +use core::{Source, Resolve, SourceId}; use core::resolver::Method; use ops; use sources::{PathSource}; @@ -91,6 +91,33 @@ pub fn update_lockfile(manifest_path: &Path, Method::Everything, Some(&previous_resolve), Some(&to_avoid))); + + // Summarize what is changing for the user. + let print_change = |status: &str, msg: String| { + opts.config.shell().status(status, msg) + }; + for (removed, added) in compare_dependency_graphs(&previous_resolve, &resolve) { + if removed.len() == 1 && added.len() == 1 { + if removed[0].source_id().is_git() { + try!(print_change("Updating", format!("{} -> #{}", + removed[0], + &added[0].source_id().precise().unwrap()[..8]))); + } else { + try!(print_change("Updating", format!("{} -> v{}", + removed[0], + added[0].version()))); + } + } + else { + for package in removed.iter() { + try!(print_change("Removing", format!("{}", package))); + } + for package in added.iter() { + try!(print_change("Adding", format!("{}", package))); + } + } + } + try!(ops::write_pkg_lockfile(&package, &resolve)); return Ok(()); @@ -108,4 +135,46 @@ pub fn update_lockfile(manifest_path: &Path, None => {} } } + + fn compare_dependency_graphs<'a>(previous_resolve: &'a Resolve, + resolve: &'a Resolve) -> + Vec<(Vec<&'a PackageId>, Vec<&'a PackageId>)> { + // Map (package name, package source) to (removed versions, added versions). + fn changes_key<'a>(dep: &'a PackageId) -> (&'a str, &'a SourceId) { + (dep.name(), dep.source_id()) + } + + fn vec_subtract(a: &[T], b: &[T]) -> Vec + where T: Ord + Clone { + let mut result = a.to_owned(); + let mut b = b.to_owned(); + b.sort(); + result.retain(|x| b.binary_search(x).is_err()); + result + } + + let mut changes = HashMap::new(); + + for dep in previous_resolve.iter() { + changes.insert(changes_key(dep), (vec![dep], vec![])); + } + for dep in resolve.iter() { + let (_, ref mut added) = *changes.entry(changes_key(dep)) + .or_insert_with(|| (vec![], vec![])); + added.push(dep); + } + + for (_, v) in changes.iter_mut() { + let (ref mut old, ref mut new) = *v; + let removed = vec_subtract(old, new); + let added = vec_subtract(new, old); + *old = removed; + *new = added; + } + + // Sort the packages by their names. + let mut packages: Vec<_> = changes.keys().map(|x| *x).collect(); + packages.sort(); + packages.iter().map(|k| changes[k].clone()).collect() + } } diff --git a/tests/support/mod.rs b/tests/support/mod.rs index d07ef594720..0f7602e99da 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -522,6 +522,8 @@ pub static RUNNING: &'static str = " Running"; pub static COMPILING: &'static str = " Compiling"; pub static FRESH: &'static str = " Fresh"; pub static UPDATING: &'static str = " Updating"; +pub static ADDING: &'static str = " Adding"; +pub static REMOVING: &'static str = " Removing"; pub static DOCTEST: &'static str = " Doc-tests"; pub static PACKAGING: &'static str = " Packaging"; pub static DOWNLOADING: &'static str = " Downloading"; diff --git a/tests/test_cargo_compile_git_deps.rs b/tests/test_cargo_compile_git_deps.rs index 834b294bb51..3cf30a2f27e 100644 --- a/tests/test_cargo_compile_git_deps.rs +++ b/tests/test_cargo_compile_git_deps.rs @@ -539,9 +539,12 @@ test!(recompilation { // Update the dependency and carry on! assert_that(p.cargo("update"), - execs().with_stdout(&format!("{} git repository `{}`", + execs().with_stdout(&format!("{} git repository `{}`\n\ + {} bar v0.5.0 ([..]) -> #[..]\n\ + ", UPDATING, - git_project.url()))); + git_project.url(), + UPDATING))); println!("going for the last compile"); assert_that(p.cargo("build"), execs().with_stdout(&format!("{} bar v0.5.0 ({}#[..])\n\ @@ -657,9 +660,12 @@ test!(update_with_shared_deps { assert_that(p.cargo("update") .arg("-p").arg("dep1") .arg("--aggressive"), - execs().with_stdout(&format!("{} git repository `{}`", + execs().with_stdout(&format!("{} git repository `{}`\n\ + {} bar v0.5.0 ([..]) -> #[..]\n\ + ", UPDATING, - git_project.url()))); + git_project.url(), + UPDATING))); // Make sure we still only compile one version of the git repo println!("build"); @@ -782,8 +788,12 @@ test!(two_deps_only_update_one { assert_that(project.cargo("update") .arg("-p").arg("dep1"), execs() - .with_stdout(&format!("{} git repository `{}`\n", - UPDATING, git1.url())) + .with_stdout(&format!("{} git repository `{}`\n\ + {} dep1 v0.5.0 ([..]) -> #[..]\n\ + ", + UPDATING, + git1.url(), + UPDATING)) .with_stderr("")); }); @@ -941,9 +951,12 @@ test!(dep_with_changed_submodule { assert_that(project.cargo("update").arg("-v"), execs() .with_stderr("") - .with_stdout(&format!("{} git repository `{}`", + .with_stdout(&format!("{} git repository `{}`\n\ + {} dep1 v0.5.0 ([..]) -> #[..]\n\ + ", UPDATING, - git_project.url()))); + git_project.url(), + UPDATING))); println!("last run"); assert_that(project.cargo("run"), execs() diff --git a/tests/test_cargo_registry.rs b/tests/test_cargo_registry.rs index 9f9426c7aa2..fef4fa5008a 100644 --- a/tests/test_cargo_registry.rs +++ b/tests/test_cargo_registry.rs @@ -3,7 +3,7 @@ use std::io::prelude::*; use cargo::util::process; use support::{project, execs, cargo_dir}; -use support::{UPDATING, DOWNLOADING, COMPILING, PACKAGING, VERIFYING}; +use support::{UPDATING, DOWNLOADING, COMPILING, PACKAGING, VERIFYING, ADDING, REMOVING}; use support::paths::{self, CargoPathExt}; use support::registry as r; use support::git; @@ -476,6 +476,7 @@ test!(update_lockfile { .arg("-p").arg("bar").arg("--precise").arg("0.0.2"), execs().with_status(0).with_stdout(&format!("\ {updating} registry `[..]` +{updating} bar v0.0.1 (registry file://[..]) -> v0.0.2 ", updating = UPDATING))); println!("0.0.2 build"); @@ -492,6 +493,7 @@ test!(update_lockfile { .arg("-p").arg("bar"), execs().with_status(0).with_stdout(&format!("\ {updating} registry `[..]` +{updating} bar v0.0.2 (registry file://[..]) -> v0.0.3 ", updating = UPDATING))); println!("0.0.3 build"); @@ -502,6 +504,27 @@ test!(update_lockfile { {compiling} foo v0.0.1 ({dir}) ", downloading = DOWNLOADING, compiling = COMPILING, dir = p.url()))); + + println!("new dependencies update"); + r::mock_pkg("bar", "0.0.4", &[("spam", "0.2.5", "")]); + r::mock_pkg("spam", "0.2.5", &[]); + assert_that(p.cargo("update") + .arg("-p").arg("bar"), + execs().with_status(0).with_stdout(&format!("\ +{updating} registry `[..]` +{updating} bar v0.0.3 (registry file://[..]) -> v0.0.4 +{adding} spam v0.2.5 (registry file://[..]) +", updating = UPDATING, adding = ADDING))); + + println!("new dependencies update"); + r::mock_pkg("bar", "0.0.5", &[]); + assert_that(p.cargo("update") + .arg("-p").arg("bar"), + execs().with_status(0).with_stdout(&format!("\ +{updating} registry `[..]` +{updating} bar v0.0.4 (registry file://[..]) -> v0.0.5 +{removing} spam v0.2.5 (registry file://[..]) +", updating = UPDATING, removing = REMOVING))); }); test!(dev_dependency_not_used { @@ -760,6 +783,7 @@ test!(update_transitive_dependency { execs().with_status(0) .with_stdout(format!("\ {updating} registry `[..]` +{updating} b v0.1.0 (registry [..]) -> v0.1.1 ", updating = UPDATING))); assert_that(p.cargo("build"),