Outdated overrides (#2423)

* Handle overrides

* Revert debugging print

* More debugging prints

* Remove unused import

* Added --no-dependency-overrides

* Updated test golden

* Handling failing resolutions

* Log message when resolution fails

* Better json output

Co-authored-by: Sigurd Meldgaard <sigurdm@google.com>
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index 3ab8795..afa44b6 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -17,7 +17,6 @@
 import '../package_name.dart';
 import '../pubspec.dart';
 import '../solver.dart';
-import '../source.dart';
 import '../source/hosted.dart';
 
 class OutdatedCommand extends PubCommand {
@@ -54,6 +53,12 @@
       help: 'When true take dev-dependencies into account when resolving.',
     );
 
+    argParser.addFlag(
+      'dependency-overrides',
+      defaultsTo: true,
+      help: 'Show resolutions with `dependency_override`',
+    );
+
     argParser.addOption('mark',
         help: 'Highlight packages with some property in the report.',
         valueHelp: 'OPTION',
@@ -67,37 +72,30 @@
     entrypoint.assertUpToDate();
 
     final includeDevDependencies = argResults['dev-dependencies'];
+    final includeDependencyOverrides = argResults['dependency-overrides'];
 
-    final upgradePubspec = includeDevDependencies
+    final rootPubspec = includeDependencyOverrides
         ? entrypoint.root.pubspec
-        : _stripDevDependencies(entrypoint.root.pubspec);
+        : _stripDependencyOverrides(entrypoint.root.pubspec);
 
-    var resolvablePubspec = _stripVersionConstraints(upgradePubspec);
+    final upgradablePubspec = includeDevDependencies
+        ? rootPubspec
+        : _stripDevDependencies(rootPubspec);
+
+    final resolvablePubspec = _stripVersionConstraints(upgradablePubspec);
 
     List<PackageId> upgradablePackages;
     List<PackageId> resolvablePackages;
 
-    Future<void> resolve() async {
-      upgradablePackages = (await resolveVersions(
-        SolveType.UPGRADE,
-        cache,
-        Package.inMemory(upgradePubspec),
-      ))
-          .packages;
-
-      resolvablePackages = (await resolveVersions(
-        SolveType.UPGRADE,
-        cache,
-        Package.inMemory(resolvablePubspec),
-      ))
-          .packages;
-    }
-
     final shouldShowSpinner = stdout.hasTerminal && !argResults['json'];
     if (shouldShowSpinner) {
-      await log.spinner('Resolving', resolve);
+      await log.spinner('Resolving', () async {
+        upgradablePackages = await _tryResolve(upgradablePubspec);
+        resolvablePackages = await _tryResolve(resolvablePubspec);
+      });
     } else {
-      await resolve();
+      upgradablePackages = await _tryResolve(upgradablePubspec);
+      resolvablePackages = await _tryResolve(resolvablePubspec);
     }
 
     final currentPackages = entrypoint.lockFile.packages.values;
@@ -106,62 +104,87 @@
     /// closure of the non-dev dependencies from the root in at least one of
     /// the current, upgradable and resolvable resolutions.
     final nonDevDependencies = <String>{
-      ...await nonDevDependencyClosure(entrypoint.root, currentPackages),
-      ...await nonDevDependencyClosure(entrypoint.root, upgradablePackages),
-      ...await nonDevDependencyClosure(entrypoint.root, resolvablePackages)
+      ...await _nonDevDependencyClosure(entrypoint.root, currentPackages),
+      ...await _nonDevDependencyClosure(entrypoint.root, upgradablePackages),
+      ...await _nonDevDependencyClosure(entrypoint.root, resolvablePackages),
     };
 
     Future<_PackageDetails> analyzeDependency(PackageRef packageRef) async {
       final name = packageRef.name;
-      final current = (entrypoint.lockFile?.packages ?? {})[name]?.version;
-      final source = packageRef.source;
-      final available = (await cache.source(source).doGetVersions(packageRef))
-          .map((id) => id.version)
-          .toList()
-            ..sort(argResults['pre-releases'] ? null : Version.prioritize);
-      final upgradable = upgradablePackages
-          .firstWhere((id) => id.name == name, orElse: () => null)
-          ?.version;
-      final resolvable = resolvablePackages
-          .firstWhere((id) => id.name == name, orElse: () => null)
-          ?.version;
-      final latest = available.last;
-      final description = packageRef.description;
+      final current = (entrypoint.lockFile?.packages ?? {})[name];
+
+      final upgradable = upgradablePackages.firstWhere((id) => id.name == name,
+          orElse: () => null);
+      final resolvable = resolvablePackages.firstWhere((id) => id.name == name,
+          orElse: () => null);
+
+      // Find the latest version, and if it's overridden.
+      var latestIsOverridden = false;
+      PackageId latest;
+      // If not overridden in current resolution we can use this
+      if (!entrypoint.root.pubspec.dependencyOverrides.containsKey(name)) {
+        latest ??= await _getLatest(current);
+      }
+      // If present as a dependency or dev_dependency we use this
+      latest ??= await _getLatest(rootPubspec.dependencies[name]);
+      latest ??= await _getLatest(rootPubspec.devDependencies[name]);
+      // If not overridden and present in either upgradable or resolvable we
+      // use this reference to find the latest
+      if (!upgradablePubspec.dependencyOverrides.containsKey(name)) {
+        latest ??= await _getLatest(upgradable);
+      }
+      if (!resolvablePubspec.dependencyOverrides.containsKey(name)) {
+        latest ??= await _getLatest(resolvable);
+      }
+      // Otherwise, we might simply not have a latest, when a transitive
+      // dependency is overridden the source can depend on which versions we
+      // are picking. This is not a problem on `pub.dev` because it does not
+      // allow 3rd party pub servers, but other servers might. Hence, we choose
+      // to fallback to using the overridden source for latest.
+      if (latest == null) {
+        latest ??= await _getLatest(current ?? upgradable ?? resolvable);
+        latestIsOverridden = true;
+      }
+
       return _PackageDetails(
-          name,
-          await _describeVersion(name, source, description, current),
-          await _describeVersion(name, source, description, upgradable),
-          await _describeVersion(name, source, description, resolvable),
-          await _describeVersion(name, source, description, latest),
-          _kind(name, entrypoint, nonDevDependencies));
+        name,
+        await _describeVersion(
+          current,
+          entrypoint.root.pubspec.dependencyOverrides.containsKey(name),
+        ),
+        await _describeVersion(
+          upgradable,
+          upgradablePubspec.dependencyOverrides.containsKey(name),
+        ),
+        await _describeVersion(
+          resolvable,
+          resolvablePubspec.dependencyOverrides.containsKey(name),
+        ),
+        await _describeVersion(
+          latest,
+          latestIsOverridden,
+        ),
+        _kind(name, entrypoint, nonDevDependencies),
+      );
     }
 
     final rows = <_PackageDetails>[];
 
-    final immediateDependencies = entrypoint.root.immediateDependencies.values;
-
-    for (final packageRange in immediateDependencies) {
-      rows.add(await analyzeDependency(packageRange.toRef()));
-    }
-
-    // Now add transitive dependencies:
     final visited = <String>{
       entrypoint.root.name,
-      ...immediateDependencies.map((d) => d.name)
     };
+    // Add all dependencies from the lockfile.
     for (final id in [
       ...currentPackages,
       ...upgradablePackages,
       ...resolvablePackages
     ]) {
-      final name = id.name;
-      if (!visited.add(name)) continue;
+      if (!visited.add(id.name)) continue;
       rows.add(await analyzeDependency(id.toRef()));
     }
 
     if (!argResults['up-to-date']) {
-      rows.retainWhere(
-          (r) => (r.current ?? r.upgradable)?.version != r.latest?.version);
+      rows.retainWhere((r) => (r.current ?? r.upgradable) != r.latest);
     }
     if (!includeDevDependencies) {
       rows.removeWhere((r) => r.kind == _DependencyKind.dev);
@@ -187,32 +210,88 @@
     }
   }
 
+  /// Get the latest version of [package].
+  ///
+  /// Returns `null`, if unable to find the package.
+  Future<PackageId> _getLatest(PackageName package) async {
+    if (package == null) {
+      return null;
+    }
+    final ref = package.toRef();
+    final available = await cache.source(ref.source).getVersions(ref);
+    if (available.isEmpty) {
+      return null;
+    }
+    available.sort(argResults['pre-releases']
+        ? (x, y) => x.version.compareTo(y.version)
+        : (x, y) => Version.prioritize(x.version, y.version));
+    return available.last;
+  }
+
   /// Retrieves the pubspec of package [name] in [version] from [source].
-  Future<Pubspec> _describeVersion(
-      String name, Source source, dynamic description, Version version) async {
-    return version == null
-        ? null
-        : await cache
-            .source(source)
-            .describe(PackageId(name, source, version, description));
+  ///
+  /// Returns `null`, if given `null` as a convinience.
+  Future<_VersionDetails> _describeVersion(
+    PackageId id,
+    bool isOverridden,
+  ) async {
+    if (id == null) {
+      return null;
+    }
+    return _VersionDetails(
+      await cache.source(id.source).describe(id),
+      id,
+      isOverridden,
+    );
   }
 
   /// Computes the closure of the graph of dependencies (not including
-  /// dev_dependencies from [root], given the package versions in [resolution].
-  Future<Set<String>> nonDevDependencyClosure(
-      Package root, Iterable<PackageId> resolution) async {
-    final mapping =
-        Map<String, PackageId>.fromIterable(resolution, key: (id) => id.name);
-    final visited = <String>{root.name};
-    final toVisit = [...root.dependencies.keys];
-    while (toVisit.isNotEmpty) {
-      final name = toVisit.removeLast();
-      if (!visited.add(name)) continue;
-      final id = mapping[name];
-      toVisit.addAll(
-          (await cache.source(id.source).describe(id)).dependencies.keys);
+  /// `dev_dependencies` from [root], given the package versions
+  /// in [resolution].
+  ///
+  /// The [resolution] is allowed to be a partial (or empty) resolution not
+  /// satisfying all the dependencies of [root].
+  Future<Set<String>> _nonDevDependencyClosure(
+    Package root,
+    Iterable<PackageId> resolution,
+  ) async {
+    final nameToId = Map<String, PackageId>.fromIterable(
+      resolution,
+      key: (id) => id.name,
+    );
+
+    final nonDevDependencies = <String>{root.name};
+    final queue = [...root.dependencies.keys];
+
+    while (queue.isNotEmpty) {
+      final name = queue.removeLast();
+      if (!nonDevDependencies.add(name)) {
+        continue;
+      }
+
+      final id = nameToId[name];
+      if (id == null) {
+        continue; // allow partial resolutions
+      }
+      final pubspec = await cache.source(id.source).describe(id);
+      queue.addAll(pubspec.dependencies.keys);
     }
-    return visited;
+
+    return nonDevDependencies;
+  }
+
+  /// Try to solve [pubspec] return [PackageId]'s in the resolution or `null`.
+  Future<List<PackageId>> _tryResolve(Pubspec pubspec) async {
+    try {
+      return (await resolveVersions(
+        SolveType.UPGRADE,
+        cache,
+        Package.inMemory(pubspec),
+      ))
+          .packages;
+    } on SolveFailure {
+      return [];
+    }
   }
 }
 
@@ -223,7 +302,18 @@
     sdkConstraints: original.sdkConstraints,
     dependencies: original.dependencies.values,
     devDependencies: [], // explicitly give empty list, to prevent lazy parsing
-    // TODO(sigurdm): consider dependency overrides.
+    dependencyOverrides: original.dependencyOverrides.values,
+  );
+}
+
+Pubspec _stripDependencyOverrides(Pubspec original) {
+  return Pubspec(
+    original.name,
+    version: original.version,
+    sdkConstraints: original.sdkConstraints,
+    dependencies: original.dependencies.values,
+    devDependencies: original.devDependencies.values,
+    dependencyOverrides: [],
   );
 }
 
@@ -254,7 +344,7 @@
     sdkConstraints: original.sdkConstraints,
     dependencies: _unconstrained(original.dependencies),
     devDependencies: _unconstrained(original.devDependencies),
-    // TODO(sigurdm): consider dependency overrides.
+    dependencyOverrides: original.dependencyOverrides.values,
   );
 }
 
@@ -263,9 +353,12 @@
       .convert({'packages': rows.map((row) => row.toJson()).toList()}));
 }
 
-Future<void> _outputHuman(List<_PackageDetails> rows,
-    Future<List<_FormattedString>> Function(_PackageDetails) marker,
-    {@required bool useColors, @required bool includeDevDependencies}) async {
+Future<void> _outputHuman(
+  List<_PackageDetails> rows,
+  Future<List<_FormattedString>> Function(_PackageDetails) marker, {
+  @required bool useColors,
+  @required bool includeDevDependencies,
+}) async {
   if (rows.isEmpty) {
     log.message('Found no outdated packages.');
     return;
@@ -343,7 +436,7 @@
 
   if (upgradable != 0) {
     if (upgradable == 1) {
-      log.message('1 upgradable dependency is locked (in pubspec.lock) to '
+      log.message('\n1 upgradable dependency is locked (in pubspec.lock) to '
           'an older version.\n'
           'To update it, use `pub upgrade`.');
     } else {
@@ -375,28 +468,27 @@
 Future<List<_FormattedString>> oudatedMarker(
     _PackageDetails packageDetails) async {
   final cols = [_FormattedString(packageDetails.name)];
-  Version previous;
-  for (final pubspec in [
+  _VersionDetails previous;
+  for (final versionDetails in [
     packageDetails.current,
     packageDetails.upgradable,
     packageDetails.resolvable,
     packageDetails.latest
   ]) {
-    final version = pubspec?.version;
-    if (version == null) {
+    if (versionDetails == null) {
       cols.add(_raw('-'));
     } else {
-      final isLatest = version == packageDetails.latest.version;
+      final isLatest = versionDetails == packageDetails.latest;
       String Function(String) color;
       if (isLatest) {
-        color = version == previous ? color = log.gray : null;
+        color = versionDetails == previous ? color = log.gray : null;
       } else {
         color = log.red;
       }
       final prefix = isLatest ? '' : '*';
-      cols.add(_format(version?.toString() ?? '-', color, prefix: prefix));
+      cols.add(_format(versionDetails.describe ?? '-', color, prefix: prefix));
     }
-    previous = version;
+    previous = versionDetails;
   }
   return cols;
 }
@@ -410,16 +502,46 @@
       packageDetails.upgradable,
       packageDetails.resolvable,
       packageDetails.latest,
-    ].map((p) => _raw(p?.version?.toString() ?? '-'))
+    ].map((p) => _raw(p?.describe ?? '-'))
   ];
 }
 
+/// Details about a single version of a package.
+class _VersionDetails {
+  final Pubspec _pubspec;
+
+  /// True if this version is overridden.
+  final bool _overridden;
+  final PackageId _id;
+  _VersionDetails(this._pubspec, this._id, this._overridden);
+
+  /// A string representation of this version to include in the outdated report.
+  String get describe {
+    final version = _pubspec.version;
+    final suffix = _overridden ? ' (overridden)' : '';
+    return '$version$suffix';
+  }
+
+  Map<String, Object> toJson() => {
+        'version': _pubspec.version.toString(),
+        if (_overridden) 'overridden': true,
+      };
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is _VersionDetails &&
+          _overridden == other._overridden &&
+          _id.source == other._id.source &&
+          _pubspec.version == other._pubspec.version;
+}
+
 class _PackageDetails implements Comparable<_PackageDetails> {
   final String name;
-  final Pubspec current;
-  final Pubspec upgradable;
-  final Pubspec resolvable;
-  final Pubspec latest;
+  final _VersionDetails current;
+  final _VersionDetails upgradable;
+  final _VersionDetails resolvable;
+  final _VersionDetails latest;
   final _DependencyKind kind;
 
   _PackageDetails(this.name, this.current, this.upgradable, this.resolvable,
@@ -436,10 +558,10 @@
   Map<String, Object> toJson() {
     return {
       'package': name,
-      'current': {'version': current?.version?.toString()},
-      'upgradable': {'version': upgradable?.version?.toString()},
-      'resolvable': {'version': resolvable?.version?.toString()},
-      'latest': {'version': latest?.version?.toString()},
+      'current': current?.toJson(),
+      'upgradable': upgradable?.toJson(),
+      'resolvable': resolvable?.toJson(),
+      'latest': latest?.toJson(),
     };
   }
 }
diff --git a/test/outdated/goldens/circular_dependencies.txt b/test/outdated/goldens/circular_dependencies.txt
index 1571b06..3e4bd80 100644
--- a/test/outdated/goldens/circular_dependencies.txt
+++ b/test/outdated/goldens/circular_dependencies.txt
@@ -28,6 +28,7 @@
 transitive dependencies: all up-to-date
 
 transitive dev_dependencies: all up-to-date
+
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `pub upgrade`.
 
@@ -40,6 +41,7 @@
 transitive dependencies: all up-to-date
 
 transitive dev_dependencies: all up-to-date
+
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `pub upgrade`.
 
@@ -52,6 +54,7 @@
 transitive dependencies: all up-to-date
 
 transitive dev_dependencies: all up-to-date
+
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `pub upgrade`.
 
@@ -64,6 +67,7 @@
 transitive dependencies: all up-to-date
 
 transitive dev_dependencies: all up-to-date
+
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `pub upgrade`.
 
@@ -72,6 +76,20 @@
 foo           *1.2.3   1.3.0       1.3.0       1.3.0   
 
 transitive dependencies: all up-to-date
+
+1 upgradable dependency is locked (in pubspec.lock) to an older version.
+To update it, use `pub upgrade`.
+
+$ pub outdated --no-color --no-dependency-overrides
+Dependencies  Current  Upgradable  Resolvable  Latest  
+foo           *1.2.3   1.3.0       1.3.0       1.3.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
 1 upgradable dependency is locked (in pubspec.lock) to an older version.
 To update it, use `pub upgrade`.
 
diff --git a/test/outdated/goldens/dependency_overrides.txt b/test/outdated/goldens/dependency_overrides.txt
new file mode 100644
index 0000000..95e1a57
--- /dev/null
+++ b/test/outdated/goldens/dependency_overrides.txt
@@ -0,0 +1,149 @@
+$ pub outdated --json
+{
+  "packages": [
+    {
+      "package": "bar",
+      "current": {
+        "version": "1.0.1",
+        "overridden": true
+      },
+      "upgradable": {
+        "version": "1.0.1",
+        "overridden": true
+      },
+      "resolvable": {
+        "version": "1.0.1",
+        "overridden": true
+      },
+      "latest": {
+        "version": "2.0.0"
+      }
+    },
+    {
+      "package": "baz",
+      "current": {
+        "version": "2.0.0",
+        "overridden": true
+      },
+      "upgradable": {
+        "version": "2.0.0",
+        "overridden": true
+      },
+      "resolvable": {
+        "version": "2.0.0",
+        "overridden": true
+      },
+      "latest": {
+        "version": "2.0.0"
+      }
+    },
+    {
+      "package": "foo",
+      "current": {
+        "version": "1.0.1",
+        "overridden": true
+      },
+      "upgradable": {
+        "version": "1.0.1",
+        "overridden": true
+      },
+      "resolvable": {
+        "version": "1.0.1",
+        "overridden": true
+      },
+      "latest": {
+        "version": "2.0.0"
+      }
+    }
+  ]
+}
+
+$ pub outdated --no-color
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+baz           *2.0.0 (overridden)  *2.0.0 (overridden)  *2.0.0 (overridden)  2.0.0   
+foo           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --mark=none
+Dependencies  Current             Upgradable          Resolvable          Latest  
+bar           1.0.1 (overridden)  1.0.1 (overridden)  1.0.1 (overridden)  2.0.0   
+baz           2.0.0 (overridden)  2.0.0 (overridden)  2.0.0 (overridden)  2.0.0   
+foo           1.0.1 (overridden)  1.0.1 (overridden)  1.0.1 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --up-to-date
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+baz           *2.0.0 (overridden)  *2.0.0 (overridden)  *2.0.0 (overridden)  2.0.0   
+foo           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --pre-releases
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+baz           *2.0.0 (overridden)  *2.0.0 (overridden)  *2.0.0 (overridden)  2.0.0   
+foo           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --no-dev-dependencies
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+baz           *2.0.0 (overridden)  *2.0.0 (overridden)  *2.0.0 (overridden)  2.0.0   
+foo           *1.0.1 (overridden)  *1.0.1 (overridden)  *1.0.1 (overridden)  2.0.0   
+
+transitive dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --no-dependency-overrides
+Dependencies  Current              Upgradable  Resolvable  Latest  
+bar           *1.0.1 (overridden)  2.0.0       2.0.0       2.0.0   
+baz           *2.0.0 (overridden)  *1.0.0      2.0.0       2.0.0   
+foo           *1.0.1 (overridden)  *1.0.0      *1.0.0      2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+3 upgradable dependencies are locked (in pubspec.lock) to older versions.
+To update these dependencies, use `pub upgrade`.
+
+1 dependency is constrained to a version that is older than a resolvable version.
+To update it, edit pubspec.yaml.
+
diff --git a/test/outdated/goldens/dependency_overrides_no_solution.txt b/test/outdated/goldens/dependency_overrides_no_solution.txt
new file mode 100644
index 0000000..dbf27fb
--- /dev/null
+++ b/test/outdated/goldens/dependency_overrides_no_solution.txt
@@ -0,0 +1,122 @@
+$ pub outdated --json
+{
+  "packages": [
+    {
+      "package": "bar",
+      "current": {
+        "version": "1.0.0",
+        "overridden": true
+      },
+      "upgradable": {
+        "version": "1.0.0",
+        "overridden": true
+      },
+      "resolvable": {
+        "version": "1.0.0",
+        "overridden": true
+      },
+      "latest": {
+        "version": "2.0.0"
+      }
+    },
+    {
+      "package": "foo",
+      "current": {
+        "version": "1.0.0",
+        "overridden": true
+      },
+      "upgradable": {
+        "version": "1.0.0",
+        "overridden": true
+      },
+      "resolvable": {
+        "version": "1.0.0",
+        "overridden": true
+      },
+      "latest": {
+        "version": "2.0.0"
+      }
+    }
+  ]
+}
+
+$ pub outdated --no-color
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+foo           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --mark=none
+Dependencies  Current             Upgradable          Resolvable          Latest  
+bar           1.0.0 (overridden)  1.0.0 (overridden)  1.0.0 (overridden)  2.0.0   
+foo           1.0.0 (overridden)  1.0.0 (overridden)  1.0.0 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --up-to-date
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+foo           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --pre-releases
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+foo           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --no-dev-dependencies
+Dependencies  Current              Upgradable           Resolvable           Latest  
+bar           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+foo           *1.0.0 (overridden)  *1.0.0 (overridden)  *1.0.0 (overridden)  2.0.0   
+
+transitive dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
+$ pub outdated --no-color --no-dependency-overrides
+Dependencies  Current              Upgradable  Resolvable  Latest  
+bar           *1.0.0 (overridden)  -           -           2.0.0   
+foo           *1.0.0 (overridden)  -           -           2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
diff --git a/test/outdated/goldens/mutually_incompatible.txt b/test/outdated/goldens/mutually_incompatible.txt
index 1c93718..f2b8a82 100644
--- a/test/outdated/goldens/mutually_incompatible.txt
+++ b/test/outdated/goldens/mutually_incompatible.txt
@@ -100,3 +100,17 @@
 Dependencies are all on the latest resolvable versions.
 Newer versions, while available, are not mutually compatible.
 
+$ pub outdated --no-color --no-dependency-overrides
+Dependencies  Current  Upgradable  Resolvable  Latest  
+bar           *1.0.0   *1.0.0      *1.0.0      2.0.0   
+foo           *1.0.0   *1.0.0      *1.0.0      2.0.0   
+
+dev_dependencies: all up-to-date
+
+transitive dependencies: all up-to-date
+
+transitive dev_dependencies: all up-to-date
+
+Dependencies are all on the latest resolvable versions.
+Newer versions, while available, are not mutually compatible.
+
diff --git a/test/outdated/goldens/newer_versions.txt b/test/outdated/goldens/newer_versions.txt
index b55af2d..8f43ef0 100644
--- a/test/outdated/goldens/newer_versions.txt
+++ b/test/outdated/goldens/newer_versions.txt
@@ -48,12 +48,8 @@
     },
     {
       "package": "transitive2",
-      "current": {
-        "version": null
-      },
-      "upgradable": {
-        "version": null
-      },
+      "current": null,
+      "upgradable": null,
       "resolvable": {
         "version": "1.0.0"
       },
@@ -63,12 +59,8 @@
     },
     {
       "package": "transitive3",
-      "current": {
-        "version": null
-      },
-      "upgradable": {
-        "version": null
-      },
+      "current": null,
+      "upgradable": null,
       "resolvable": {
         "version": "1.0.0"
       },
@@ -174,3 +166,23 @@
 1 dependency is constrained to a version that is older than a resolvable version.
 To update it, edit pubspec.yaml.
 
+$ pub outdated --no-color --no-dependency-overrides
+Dependencies  Current  Upgradable  Resolvable  Latest  
+foo           *1.2.3   *1.3.0      *2.0.0      3.0.0   
+
+dev_dependencies
+builder       *1.2.3   *1.3.0      2.0.0       2.0.0   
+
+transitive dependencies
+transitive    *1.2.3   *1.3.0      *1.3.0      2.0.0   
+transitive2   -        -           1.0.0       1.0.0   
+
+transitive dev_dependencies
+transitive3   -        -           1.0.0       1.0.0   
+
+3 upgradable dependencies are locked (in pubspec.lock) to older versions.
+To update these dependencies, use `pub upgrade`.
+
+2  dependencies are constrained to versions that are older than a resolvable version.
+To update these dependencies, edit pubspec.yaml.
+
diff --git a/test/outdated/goldens/no_dependencies.txt b/test/outdated/goldens/no_dependencies.txt
index c27c6d5..bfd5e07 100644
--- a/test/outdated/goldens/no_dependencies.txt
+++ b/test/outdated/goldens/no_dependencies.txt
@@ -18,3 +18,6 @@
 $ pub outdated --no-color --no-dev-dependencies
 Found no outdated packages.
 
+$ pub outdated --no-color --no-dependency-overrides
+Found no outdated packages.
+
diff --git a/test/outdated/outdated_test.dart b/test/outdated/outdated_test.dart
index 63b658e..55e9692 100644
--- a/test/outdated/outdated_test.dart
+++ b/test/outdated/outdated_test.dart
@@ -19,6 +19,7 @@
     ['--no-color', '--up-to-date'],
     ['--no-color', '--pre-releases'],
     ['--no-color', '--no-dev-dependencies'],
+    ['--no-color', '--no-dependency-overrides'],
   ]) {
     final process = await startPub(args: ['outdated', ...args]);
     await process.shouldExit(0);
@@ -125,4 +126,78 @@
 
     await variations('mutually_incompatible');
   });
+
+  test('overridden dependencies', () async {
+    ensureGit();
+    await servePackages(
+      (builder) => builder
+        ..serve('foo', '1.0.0')
+        ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
+        ..serve('bar', '1.0.0')
+        ..serve('bar', '2.0.0')
+        ..serve('baz', '1.0.0')
+        ..serve('baz', '2.0.0'),
+    );
+
+    await d.git('foo.git', [
+      d.libPubspec('foo', '1.0.1'),
+    ]).create();
+
+    await d.dir('bar', [
+      d.libPubspec('bar', '1.0.1'),
+    ]).create();
+
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'app',
+        'version': '1.0.1',
+        'dependencies': {
+          'foo': '^1.0.0',
+          'bar': '^2.0.0',
+          'baz': '^1.0.0',
+        },
+        'dependency_overrides': {
+          'foo': {
+            'git': {'url': '../foo.git'}
+          },
+          'bar': {'path': '../bar'},
+          'baz': '2.0.0'
+        },
+      })
+    ]).create();
+
+    await pubGet();
+
+    await variations('dependency_overrides');
+  });
+
+  test('overridden dependencies - no resolution', () async {
+    ensureGit();
+    await servePackages(
+      (builder) => builder
+        ..serve('foo', '1.0.0', deps: {'bar': '^2.0.0'})
+        ..serve('foo', '2.0.0', deps: {'bar': '^1.0.0'})
+        ..serve('bar', '1.0.0', deps: {'foo': '^1.0.0'})
+        ..serve('bar', '2.0.0', deps: {'foo': '^2.0.0'}),
+    );
+
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'app',
+        'version': '1.0.1',
+        'dependencies': {
+          'foo': 'any',
+          'bar': 'any',
+        },
+        'dependency_overrides': {
+          'foo': '1.0.0',
+          'bar': '1.0.0',
+        },
+      })
+    ]).create();
+
+    await pubGet();
+
+    await variations('dependency_overrides_no_solution');
+  });
 }