Show summary count of outdated packages after running `pub upgrade`  (#2444)

diff --git a/lib/src/solver/report.dart b/lib/src/solver/report.dart
index 4f7318d..b175d17 100644
--- a/lib/src/solver/report.dart
+++ b/lib/src/solver/report.dart
@@ -130,6 +130,32 @@
     }
   }
 
+  /// Displays a two-line message, number of outdated packages and an
+  /// instruction to run `pub outdated` if outdated packages are detected.
+  void reportOutdated() {
+    final outdatedPackagesCount = _result.packages.where((id) {
+      final versions = _result.availableVersions[id.name];
+      // A version is counted:
+      // - if there is a newer version which is not a pre-release and current
+      // version is also not a pre-release or,
+      // - if the current version is pre-release then any upgraded version is
+      // considered.
+      return versions.any((v) =>
+          v > id.version && (id.version.isPreRelease || !v.isPreRelease));
+    }).length;
+
+    if (outdatedPackagesCount > 0) {
+      String packageCountString;
+      if (outdatedPackagesCount == 1) {
+        packageCountString = '1 package has';
+      } else {
+        packageCountString = '$outdatedPackagesCount packages have';
+      }
+      log.message('$packageCountString newer versions incompatible with '
+          'dependency constraints.\nTry `pub outdated` for more information.');
+    }
+  }
+
   /// Reports the results of the upgrade on the package named [name].
   ///
   /// If [alwaysShow] is true, the package is reported even if it didn't change,
diff --git a/lib/src/solver/result.dart b/lib/src/solver/result.dart
index 94da92e..7c2b5a5 100644
--- a/lib/src/solver/result.dart
+++ b/lib/src/solver/result.dart
@@ -94,10 +94,16 @@
   /// Displays a one-line message summarizing what changes were made (or would
   /// be made) to the lockfile.
   ///
+  /// If [type] is `SolveType.UPGRADE` it also shows the number of packages
+  /// that are not at the latest available version.
+  ///
   /// [type] is the type of version resolution that was run.
   void summarizeChanges(SolveType type, {bool dryRun = false}) {
-    SolveReport(type, _sources, _root, _previousLockFile, this)
-        .summarize(dryRun: dryRun);
+    final report = SolveReport(type, _sources, _root, _previousLockFile, this);
+    report.summarize(dryRun: dryRun);
+    if (type == SolveType.UPGRADE) {
+      report.reportOutdated();
+    }
   }
 
   @override
diff --git a/test/test_pub.dart b/test/test_pub.dart
index deb68b9..fe45ce0 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -81,8 +81,10 @@
 class RunCommand {
   static final get = RunCommand(
       'get', RegExp(r'Got dependencies!|Changed \d+ dependenc(y|ies)!'));
-  static final upgrade = RunCommand('upgrade',
-      RegExp(r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
+  static final upgrade = RunCommand('upgrade', RegExp(r'''
+(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)($|
+\d+ packages? (has|have) newer versions incompatible with dependency constraints.
+Try `pub outdated` for more information.$)'''));
   static final downgrade = RunCommand('downgrade',
       RegExp(r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
 
diff --git a/test/upgrade/report/shows_pub_outdated_test.dart b/test/upgrade/report/shows_pub_outdated_test.dart
new file mode 100644
index 0000000..1b2c2e5
--- /dev/null
+++ b/test/upgrade/report/shows_pub_outdated_test.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+void main() {
+  test('shows pub outdated', () async {
+    await servePackages((builder) {
+      builder.serve('multiple_newer', '1.0.0');
+      builder.serve('multiple_newer', '1.0.1-unstable.1');
+      builder.serve('multiple_newer', '1.0.1');
+      builder.serve('multiple_newer', '1.0.2-unstable.1');
+      builder.serve('multiple_newer', '1.0.2-unstable.2');
+      builder.serve('multiple_newer_stable', '1.0.0');
+      builder.serve('multiple_newer_stable', '1.0.1');
+      builder.serve('multiple_newer_stable', '1.0.2');
+      builder.serve('multiple_newer_unstable', '1.0.0');
+      builder.serve('multiple_newer_unstable', '1.0.1-unstable.1');
+      builder.serve('multiple_newer_unstable', '1.0.1-unstable.2');
+      builder.serve('no_newer', '1.0.0');
+      builder.serve('one_newer_unstable', '1.0.0');
+      builder.serve('one_newer_unstable', '1.0.1-unstable.1');
+      builder.serve('one_newer_stable', '1.0.0');
+      builder.serve('one_newer_stable', '1.0.1');
+    });
+
+    // Constraint everything to the first version.
+    await d.appDir({
+      'multiple_newer': '1.0.0',
+      'multiple_newer_stable': '1.0.0',
+      'multiple_newer_unstable': '1.0.0',
+      'no_newer': '1.0.0',
+      'one_newer_unstable': '1.0.0',
+      'one_newer_stable': '1.0.0'
+    }).create();
+
+    // Upgrade everything.
+    await pubUpgrade(output: RegExp(r'''
+3 packages have newer versions incompatible with dependency constraints.
+Try `pub outdated` for more information.$''', multiLine: true));
+
+    // Upgrade `multiple_newer` to `1.0.1`.
+    await d.appDir({
+      'multiple_newer': '1.0.1',
+      'multiple_newer_stable': '1.0.0',
+      'multiple_newer_unstable': '1.0.0',
+      'no_newer': '1.0.0',
+      'one_newer_unstable': '1.0.0',
+      'one_newer_stable': '1.0.0'
+    }).create();
+
+    // Upgrade everything.
+    await pubUpgrade(output: RegExp(r'''
+2 packages have newer versions incompatible with dependency constraints.
+Try `pub outdated` for more information.$''', multiLine: true));
+
+    // Upgrade `multiple_newer` to `1.0.2-unstable.1`.
+    await d.appDir({
+      'multiple_newer': '1.0.2-unstable.1',
+      'multiple_newer_stable': '1.0.0',
+      'multiple_newer_unstable': '1.0.0',
+      'no_newer': '1.0.0',
+      'one_newer_unstable': '1.0.0',
+      'one_newer_stable': '1.0.0'
+    }).create();
+
+    // Upgrade everything.
+    await pubUpgrade(output: RegExp(r'''
+3 packages have newer versions incompatible with dependency constraints.
+Try `pub outdated` for more information.$''', multiLine: true));
+
+    // Upgrade all except `one_newer_stable`.
+    await d.appDir({
+      'multiple_newer': '1.0.2-unstable.2',
+      'multiple_newer_stable': '1.0.2',
+      'multiple_newer_unstable': '1.0.1-unstable.2',
+      'no_newer': '1.0.0',
+      'one_newer_unstable': '1.0.1-unstable.1',
+      'one_newer_stable': '1.0.0'
+    }).create();
+
+    // Upgrade everything.
+    await pubUpgrade(output: RegExp(r'''
+1 package has newer versions incompatible with dependency constraints.
+Try `pub outdated` for more information.$''', multiLine: true));
+  });
+}