diff --git a/lib/src/command.dart b/lib/src/command.dart
index 1f5ac2a872f2c57708853ac44aa59247f373094a..c37dc1cad361f2db69714010dec4d526c057a7f0 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -13,6 +13,7 @@ import 'package:path/path.dart' as path;
 import 'command/build.dart';
 import 'command/cache.dart';
 import 'command/deps.dart';
+import 'command/downgrade.dart';
 import 'command/get.dart';
 import 'command/global.dart';
 import 'command/help.dart';
@@ -261,6 +262,7 @@ _initCommands() {
     'build': new BuildCommand(),
     'cache': new CacheCommand(),
     'deps': new DepsCommand(),
+    'downgrade': new DowngradeCommand(),
     'global': new GlobalCommand(),
     'get': new GetCommand(),
     'help': new HelpCommand(),
diff --git a/lib/src/command/downgrade.dart b/lib/src/command/downgrade.dart
new file mode 100644
index 0000000000000000000000000000000000000000..94e8fc6c056d3442c6937f47857e9d648ae559a8
--- /dev/null
+++ b/lib/src/command/downgrade.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2012, 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.
+
+library pub.command.downgrade;
+
+import 'dart:async';
+
+import '../command.dart';
+import '../log.dart' as log;
+import '../solver/version_solver.dart';
+
+/// Handles the `downgrade` pub command.
+class DowngradeCommand extends PubCommand {
+  String get description =>
+      "Downgrade the current package's dependencies to oldest versions.\n\n"
+      "This doesn't modify the lockfile, so it can be reset with \"pub get\".";
+  String get usage => "pub downgrade [dependencies...]";
+  bool get takesArguments => true;
+
+  bool get isOffline => commandOptions['offline'];
+
+  DowngradeCommand() {
+    commandParser.addFlag('offline',
+        help: 'Use cached packages instead of accessing the network.');
+
+    commandParser.addFlag('dry-run', abbr: 'n', negatable: false,
+        help: "Report what dependencies would change but don't change any.");
+  }
+
+  Future onRun() {
+    var dryRun = commandOptions['dry-run'];
+    return entrypoint.acquireDependencies(SolveType.DOWNGRADE,
+        useLatest: commandOptions.rest, dryRun: dryRun).then((_) {
+      if (isOffline) {
+        log.warning("Warning: Downgrading when offline may not update you to "
+            "the oldest versions of your dependencies.");
+      }
+    });
+  }
+}
diff --git a/lib/src/command/get.dart b/lib/src/command/get.dart
index 3c066fdbabae3c7ebde91eac83233c9c7ff45add..7394e0c76ae1312e8c9037d4f175af677c3e4be3 100644
--- a/lib/src/command/get.dart
+++ b/lib/src/command/get.dart
@@ -7,6 +7,7 @@ library pub.command.get;
 import 'dart:async';
 
 import '../command.dart';
+import '../solver/version_solver.dart';
 
 /// Handles the `get` pub command.
 class GetCommand extends PubCommand {
@@ -25,6 +26,7 @@ class GetCommand extends PubCommand {
   }
 
   Future onRun() {
-    return entrypoint.acquireDependencies(dryRun: commandOptions['dry-run']);
+    return entrypoint.acquireDependencies(SolveType.GET,
+        dryRun: commandOptions['dry-run']);
   }
 }
diff --git a/lib/src/command/global_deactivate.dart b/lib/src/command/global_deactivate.dart
index 047067aae30bbad80fc61125b39a332496d84da4..6d03f55519f436c42dcfa3a3d9e86280045a6abf 100644
--- a/lib/src/command/global_deactivate.dart
+++ b/lib/src/command/global_deactivate.dart
@@ -8,7 +8,6 @@ import 'dart:async';
 
 import '../command.dart';
 import '../utils.dart';
-import '../version.dart';
 
 /// Handles the `global deactivate` pub command.
 class GlobalDeactivateCommand extends PubCommand {
@@ -31,5 +30,6 @@ class GlobalDeactivateCommand extends PubCommand {
     }
 
     globals.deactivate(commandOptions.rest.first);
+    return null;
   }
 }
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index e8b6ad0bbba9a579a8a4544ed25cd73e6bc11468..c42c33b385b4df610dad2dd9fd01999158681274 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -8,6 +8,7 @@ import 'dart:async';
 
 import '../command.dart';
 import '../log.dart' as log;
+import '../solver/version_solver.dart';
 
 /// Handles the `upgrade` pub command.
 class UpgradeCommand extends PubCommand {
@@ -30,8 +31,8 @@ class UpgradeCommand extends PubCommand {
 
   Future onRun() {
     var dryRun = commandOptions['dry-run'];
-    return entrypoint.acquireDependencies(useLatest: commandOptions.rest,
-        isUpgrade: true, dryRun: dryRun).then((_) {
+    return entrypoint.acquireDependencies(SolveType.UPGRADE,
+        useLatest: commandOptions.rest, dryRun: dryRun).then((_) {
       if (isOffline) {
         log.warning("Warning: Upgrading when offline may not update you to the "
                     "latest versions of your dependencies.");
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index c866d60eab116e4c1180de96b1750a7bb27c732b..67e2ebbacec8348f8c780c7319cc10aa28a9fa2a 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -79,6 +79,8 @@ class Entrypoint {
 
   /// Gets all dependencies of the [root] package.
   ///
+  /// Performs version resolution according to [SolveType].
+  ///
   /// [useLatest], if provided, defines a list of packages that will be
   /// unlocked and forced to their latest versions. If [upgradeAll] is
   /// true, the previous lockfile is ignored and all packages are re-resolved
@@ -86,21 +88,21 @@ class Entrypoint {
   /// previously locked packages.
   ///
   /// Shows a report of the changes made relative to the previous lockfile. If
-  /// [isUpgrade] is `true`, all transitive dependencies are shown in the
-  /// report. Otherwise, only dependencies that were changed are shown. If
+  /// this is an upgrade or downgrade, all transitive dependencies are shown in
+  /// the report. Otherwise, only dependencies that were changed are shown. If
   /// [dryRun] is `true`, no physical changes are made.
-  Future acquireDependencies({List<String> useLatest, bool isUpgrade: false,
+  Future acquireDependencies(SolveType type, {List<String> useLatest,
       bool dryRun: false}) {
     return syncFuture(() {
-      return resolveVersions(cache.sources, root, lockFile: lockFile,
-          useLatest: useLatest, upgradeAll: isUpgrade && useLatest.isEmpty);
+      return resolveVersions(type, cache.sources, root, lockFile: lockFile,
+          useLatest: useLatest);
     }).then((result) {
       if (!result.succeeded) throw result.error;
 
-      result.showReport(isUpgrade: isUpgrade);
+      result.showReport(type);
 
       if (dryRun) {
-        result.summarizeChanges(isUpgrade: isUpgrade, dryRun: dryRun);
+        result.summarizeChanges(type, dryRun: dryRun);
         return null;
       }
 
@@ -110,7 +112,7 @@ class Entrypoint {
         _saveLockFile(ids);
         _linkSelf();
         _linkSecondaryPackageDirs();
-        result.summarizeChanges(isUpgrade: isUpgrade, dryRun: dryRun);
+        result.summarizeChanges(type, dryRun: dryRun);
       });
     });
   }
@@ -211,7 +213,7 @@ class Entrypoint {
       });
     }).then((upToDate) {
       if (upToDate) return null;
-      return acquireDependencies();
+      return acquireDependencies(SolveType.GET);
     });
   }
 
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 37981460b5746fce2b96e7babb6e0e9470427341..b9bb54881fc29e904daf9b3afdc69add61c7c97e 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -5,7 +5,6 @@
 library pub.global_packages;
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -78,10 +77,11 @@ class GlobalPackages {
     }).then((p) {
       package = p;
       // Resolve it and download its dependencies.
-      return resolveVersions(cache.sources, package, lockFile: lockFile);
+      return resolveVersions(SolveType.GET, cache.sources, package,
+          lockFile: lockFile);
     }).then((result) {
       if (!result.succeeded) throw result.error;
-      result.showReport();
+      result.showReport(SolveType.GET);
 
       // Make sure all of the dependencies are locally installed.
       return Future.wait(result.packages.map((id) {
diff --git a/lib/src/solver/backtracking_solver.dart b/lib/src/solver/backtracking_solver.dart
index 4c6033369bacd5a8e80cb974ac49e79548dede22..d8b57614b5ca2325509445d049feb558c2e2295b 100644
--- a/lib/src/solver/backtracking_solver.dart
+++ b/lib/src/solver/backtracking_solver.dart
@@ -59,6 +59,7 @@ import 'version_solver.dart';
 /// versions for speculative package selections. Backtracks and advances to the
 /// next potential solution in the case of a failure.
 class BacktrackingSolver {
+  final SolveType type;
   final SourceRegistry sources;
   final Package root;
 
@@ -73,9 +74,6 @@ class BacktrackingSolver {
   /// packages.
   final _forceLatest = new Set<String>();
 
-  /// If this is set, the contents of [lockFile] are ignored while solving.
-  final bool _upgradeAll;
-
   /// The set of packages whose dependecy is being overridden by the root
   /// package, keyed by the name of the package.
   ///
@@ -109,11 +107,11 @@ class BacktrackingSolver {
   int get attemptedSolutions => _attemptedSolutions;
   var _attemptedSolutions = 1;
 
-  BacktrackingSolver(SourceRegistry sources, this.root, this.lockFile,
-                     List<String> useLatest, {bool upgradeAll: false})
-      : sources = sources,
-        cache = new PubspecCache(sources),
-        _upgradeAll = upgradeAll {
+  BacktrackingSolver(SolveType type, SourceRegistry sources, this.root,
+          this.lockFile, List<String> useLatest)
+      : type = type,
+        sources = sources,
+        cache = new PubspecCache(type, sources) {
     for (var package in useLatest) {
       _forceLatest.add(package);
     }
@@ -218,9 +216,19 @@ class BacktrackingSolver {
   ///
   /// Returns `null` if it isn't in the lockfile (or has been unlocked).
   PackageId getLocked(String package) {
-    if (_upgradeAll) return null;
-    if (_forceLatest.contains(package)) return null;
+    if (type == SolveType.GET) return lockFile.packages[package];
+
+    // When downgrading, we don't want to force the latest versions of
+    // non-hosted packages, since they don't support multiple versions and thus
+    // can't be downgraded.
+    if (type == SolveType.DOWNGRADE) {
+      var locked = lockFile.packages[package];
+      if (locked != null && !sources[locked.source].hasMultipleVersions) {
+        return locked;
+      }
+    }
 
+    if (_forceLatest.isEmpty || _forceLatest.contains(package)) return null;
     return lockFile.packages[package];
   }
 
diff --git a/lib/src/solver/solve_report.dart b/lib/src/solver/solve_report.dart
index 4012bdcd74e8d960c4a11853a68864be015938ab..7fed60a7ea60453232abd95ae06997f136837b53 100644
--- a/lib/src/solver/solve_report.dart
+++ b/lib/src/solver/solve_report.dart
@@ -18,9 +18,7 @@ import 'version_solver.dart';
 ///
 /// It's a report builder.
 class SolveReport {
-  /// Whether all dependencies should be reported, or just ones that changed.
-  final bool _showAll;
-
+  final SolveType _type;
   final SourceRegistry _sources;
   final Package _root;
   final LockFile _previousLockFile;
@@ -31,9 +29,8 @@ class SolveReport {
 
   final _output = new StringBuffer();
 
-  SolveReport(this._sources, this._root, this._previousLockFile,
-      this._result, {bool showAll: false})
-      : _showAll = showAll {
+  SolveReport(this._type, this._sources, this._root, this._previousLockFile,
+      this._result) {
     // Fill the map so we can use it later.
     for (var id in _result.packages) {
       _dependencies[id.name] = id;
@@ -80,10 +77,10 @@ class SolveReport {
       }
     } else {
       if (numChanged == 0) {
-        if (_showAll) {
-          log.message("No dependencies changed.");
-        } else {
+        if (_type == SolveType.GET) {
           log.message("Got dependencies!");
+        } else {
+          log.message("No dependencies changed.");
         }
       } else if (numChanged == 1) {
         log.message("Changed $numChanged dependency!");
@@ -137,8 +134,8 @@ class SolveReport {
   /// 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,
-  /// regardless of [_showAll]. If [highlightOverride] is true (or absent),
-  /// writes "(override)" next to overridden packages.
+  /// regardless of [_type]. If [highlightOverride] is true (or absent), writes
+  /// "(override)" next to overridden packages.
   void _reportPackage(String name,
       {bool alwaysShow: false, bool highlightOverride: true}) {
     var newId = _dependencies[name];
@@ -186,7 +183,9 @@ class SolveReport {
       icon = "  ";
     }
 
-    if (!(alwaysShow || changed || addedOrRemoved || _showAll)) return;
+    if (_type == SolveType.GET && !(alwaysShow || changed || addedOrRemoved)) {
+      return;
+    }
 
     _output.write(icon);
     _output.write(log.bold(id.name));
@@ -207,8 +206,9 @@ class SolveReport {
 
     // See if there are any newer versions of the package that we were
     // unable to upgrade to.
-    if (newId != null) {
+    if (newId != null && _type != SolveType.DOWNGRADE) {
       var versions = _result.availableVersions[newId.name];
+
       var newerStable = false;
       var newerUnstable = false;
 
diff --git a/lib/src/solver/version_solver.dart b/lib/src/solver/version_solver.dart
index 9e0939c956e49dbd376f86ee391557c257370c5a..ec276ea22d0911d0c8185405897348840e056534 100644
--- a/lib/src/solver/version_solver.dart
+++ b/lib/src/solver/version_solver.dart
@@ -30,14 +30,14 @@ import 'solve_report.dart';
 /// packages.
 ///
 /// If [upgradeAll] is true, the contents of [lockFile] are ignored.
-Future<SolveResult> resolveVersions(SourceRegistry sources, Package root,
-    {LockFile lockFile, List<String> useLatest, bool upgradeAll: false}) {
+Future<SolveResult> resolveVersions(SolveType type, SourceRegistry sources,
+    Package root, {LockFile lockFile, List<String> useLatest}) {
   if (lockFile == null) lockFile = new LockFile.empty();
   if (useLatest == null) useLatest = [];
 
   return log.progress('Resolving dependencies', () {
-    return new BacktrackingSolver(sources, root, lockFile, useLatest,
-        upgradeAll: upgradeAll).solve();
+    return new BacktrackingSolver(type, sources, root, lockFile, useLatest)
+        .solve();
   });
 }
 
@@ -87,21 +87,18 @@ class SolveResult {
 
   /// Displays a report of what changes were made to the lockfile.
   ///
-  /// If [isUpgrade] is true, a "pub upgrade" was run, otherwise it was another
-  /// command.
-  void showReport({bool isUpgrade: false}) {
-    new SolveReport(_sources, _root, _previousLockFile, this,
-        showAll: isUpgrade).show();
+  /// [type] is the type of version resolution that was run.
+  void showReport(SolveType type) {
+    new SolveReport(type, _sources, _root, _previousLockFile, this).show();
   }
 
   /// Displays a one-line message summarizing what changes were made (or would
   /// be made) to the lockfile.
   ///
-  /// If [isUpgrade] is true, a "pub upgrade" was run, otherwise it was another
-  /// command.
-  void summarizeChanges({bool isUpgrade: false, bool dryRun: false}) {
-    new SolveReport(_sources, _root, _previousLockFile, this,
-        showAll: isUpgrade).summarize(dryRun: dryRun);
+  /// [type] is the type of version resolution that was run.
+  void summarizeChanges(SolveType type, {bool dryRun: false}) {
+    new SolveReport(type, _sources, _root, _previousLockFile, this)
+        .summarize(dryRun: dryRun);
   }
 
   String toString() {
@@ -130,6 +127,9 @@ class PubspecCache {
   /// The already-requested cached pubspecs.
   final _pubspecs = new Map<PackageId, Pubspec>();
 
+  /// The type of version resolution that was run.
+  final SolveType _type;
+
   /// The number of times a version list was requested and it wasn't cached and
   /// had to be requested from the source.
   int _versionCacheMisses = 0;
@@ -146,7 +146,7 @@ class PubspecCache {
   /// returned.
   int _pubspecCacheHits = 0;
 
-  PubspecCache(this._sources);
+  PubspecCache(this._type, this._sources);
 
   /// Caches [pubspec] as the [Pubspec] for the package identified by [id].
   void cache(PackageId id, Pubspec pubspec) {
@@ -205,7 +205,8 @@ class PubspecCache {
     return source.getVersions(package.name, package.description)
         .then((versions) {
       // Sort by priority so we try preferred versions first.
-      versions.sort(Version.prioritize);
+      versions.sort(_type == SolveType.DOWNGRADE ? Version.antiPrioritize :
+          Version.prioritize);
 
       var ids = versions.reversed.map(
           (version) => package.atVersion(version)).toList();
@@ -286,6 +287,27 @@ class Dependency {
   String toString() => '$depender $dependerVersion -> $dep';
 }
 
+/// An enum for types of version resolution.
+class SolveType {
+  /// As few changes to the lockfile as possible to be consistent with the
+  /// pubspec.
+  static const GET = const SolveType._("get");
+
+  /// Upgrade all packages or specific packages to the highest versions
+  /// possible, regardless of the lockfile.
+  static const UPGRADE = const SolveType._("upgrade");
+
+  /// Downgrade all packages or specific packages to the lowest versions
+  /// possible, regardless of the lockfile.
+  static const DOWNGRADE = const SolveType._("downgrade");
+
+  final String _name;
+
+  const SolveType._(this._name);
+
+  String toString() => _name;
+}
+
 /// Base class for all failures that can occur while trying to resolve versions.
 abstract class SolveFailure implements ApplicationException {
   /// The name of the package whose version could not be solved.
diff --git a/lib/src/source.dart b/lib/src/source.dart
index 0235792e09bae5f34aae4f07eccea0e4bd17199d..4152384e1589d4fa67999c1ce7c4191afb9f374e 100644
--- a/lib/src/source.dart
+++ b/lib/src/source.dart
@@ -27,6 +27,12 @@ abstract class Source {
   /// all sources.
   String get name;
 
+  /// Whether this source can choose between multiple versions of the same
+  /// package during version solving.
+  ///
+  /// Defaults to `false`.
+  final bool hasMultipleVersions = false;
+
   /// Whether or not this source is the default source.
   bool get isDefault => systemCache.sources.defaultSource == this;
 
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index ec21d2080899105b026cca63c29459651116a558..5dcba33efcffd135d2d7b46745fee0a8ff704039 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -25,6 +25,7 @@ import 'cached.dart';
 /// the same API as pub.dartlang.org.
 class HostedSource extends CachedSource {
   final name = "hosted";
+  final hasMultipleVersions = true;
 
   /// Gets the default URL for the package server for hosted dependencies.
   static String get defaultUrl {
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 3c2e1aa31f581d2b024527bd525a7032f1b23947..4e1a369bfd1335643b152271cc3aa22363abcf8a 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -368,6 +368,15 @@ maxAll(Iterable iter, [int compare(element1, element2)]) {
       compare(element, max) > 0 ? element : max);
 }
 
+/// Returns the minimum value in [iter] by [compare].
+///
+/// [compare] defaults to [Comparable.compare].
+minAll(Iterable iter, [int compare(element1, element2)]) {
+  if (compare == null) compare = Comparable.compare;
+  return iter.reduce((max, element) =>
+      compare(element, max) < 0 ? element : max);
+}
+
 /// Replace each instance of [matcher] in [source] with the return value of
 /// [fn].
 String replace(String source, Pattern matcher, String fn(Match)) {
diff --git a/lib/src/version.dart b/lib/src/version.dart
index 7e33fe2de8e0909e22e88bcdf70c7ad9b4444888..50cfb0c57123c22b22bbf0f65164932f6e1d86f9 100644
--- a/lib/src/version.dart
+++ b/lib/src/version.dart
@@ -54,6 +54,18 @@ class Version implements Comparable<Version>, VersionConstraint {
     return a.compareTo(b);
   }
 
+  /// Like [proiritize], but lower version numbers are considered greater than
+  /// higher version numbers.
+  ///
+  /// This still considers prerelease versions to be lower than non-prerelease
+  /// versions.
+  static int antiPrioritize(Version a, Version b) {
+    if (a.isPreRelease && !b.isPreRelease) return -1;
+    if (!a.isPreRelease && b.isPreRelease) return 1;
+
+    return b.compareTo(a);
+  }
+
   /// The major version number: "1" in "1.2.3".
   final int major;
 
diff --git a/test/downgrade/does_not_show_other_versions_test.dart b/test/downgrade/does_not_show_other_versions_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e62bff04a455ff56038eda0b140c6b94af2ffa8f
--- /dev/null
+++ b/test/downgrade/does_not_show_other_versions_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2013, 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.
+
+library pub_tests;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration("does not show how many other versions are available", () {
+    servePackages([
+      packageMap("downgraded", "1.0.0"),
+      packageMap("downgraded", "2.0.0"),
+      packageMap("downgraded", "3.0.0-dev")
+    ]);
+
+    d.appDir({
+      "downgraded": "3.0.0-dev"
+    }).create();
+
+    pubGet();
+
+    // Loosen the constraints.
+    d.appDir({
+      "downgraded": ">=2.0.0"
+    }).create();
+
+    pubDowngrade(output: contains("downgraded 2.0.0 (was 3.0.0-dev)"));
+  });
+}
diff --git a/test/downgrade/doesnt_change_git_dependencies_test.dart b/test/downgrade/doesnt_change_git_dependencies_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b661c7dc4c6bef28939fcccf9c4f2a19fbcfa270
--- /dev/null
+++ b/test/downgrade/doesnt_change_git_dependencies_test.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2014, 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:path/path.dart' as path;
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/io.dart';
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration("doesn't change git dependencies", () {
+    ensureGit();
+
+    d.git('foo.git', [
+      d.libDir('foo'),
+      d.libPubspec('foo', '1.0.0')
+    ]).create();
+
+    d.appDir({
+      "foo": {"git": "../foo.git"}
+    }).create();
+
+    pubGet();
+
+    d.dir(packagesPath, [
+      d.dir('foo', [
+        d.file('foo.dart', 'main() => "foo";')
+      ])
+    ]).validate();
+
+    d.git('foo.git', [
+      d.libDir('foo', 'foo 2'),
+      d.libPubspec('foo', '1.0.0')
+    ]).commit();
+
+    pubDowngrade();
+
+    d.dir(packagesPath, [
+      d.dir('foo', [
+        d.file('foo.dart', 'main() => "foo";')
+      ])
+    ]).validate();
+  });
+}
diff --git a/test/downgrade/dry_run_does_not_apply_changes_test.dart b/test/downgrade/dry_run_does_not_apply_changes_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..4bae76351e163fe99c9e88488edd27c15f90d7e7
--- /dev/null
+++ b/test/downgrade/dry_run_does_not_apply_changes_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2014, 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:path/path.dart' as path;
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/io.dart';
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration("--dry-run shows report but does not apply changes", () {
+    servePackages([
+      packageMap("foo", "1.0.0"),
+      packageMap("foo", "2.0.0"),
+    ]);
+
+    // Create the first lockfile.
+    d.appDir({
+      "foo": "2.0.0"
+    }).create();
+
+    pubGet();
+
+    // Change the pubspec.
+    d.appDir({
+      "foo": "any"
+    }).create();
+
+    // Also delete the "packages" directory.
+    schedule(() {
+      deleteEntry(path.join(sandboxDir, appPath, "packages"));
+    });
+
+    // Do the dry run.
+    pubDowngrade(args: ["--dry-run"], output: allOf([
+      contains("< foo 1.0.0"),
+      contains("Would change 1 dependency.")
+    ]));
+
+    d.dir(appPath, [
+      // The lockfile should be unmodified.
+      d.matcherFile("pubspec.lock", contains("2.0.0")),
+      // The "packages" directory should not have been regenerated.
+      d.nothing("packages")
+    ]).validate();
+  });
+}
diff --git a/test/downgrade/unlock_dependers_test.dart b/test/downgrade/unlock_dependers_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..93a376c0416817c2c228b05419f203101128273e
--- /dev/null
+++ b/test/downgrade/unlock_dependers_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2012, 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.
+
+library pub_tests;
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration("downgrades a locked package's dependers in order to get it to "
+      "min version", () {
+    servePackages([
+      packageMap("foo", "2.0.0", {"bar": ">1.0.0"}),
+      packageMap("bar", "2.0.0")
+    ]);
+
+    d.appDir({"foo": "any", "bar": "any"}).create();
+
+    pubGet();
+
+    d.packagesDir({
+      "foo": "2.0.0",
+      "bar": "2.0.0"
+    }).validate();
+
+    servePackages([
+      packageMap("foo", "1.0.0", {"bar": "any"}),
+      packageMap("bar", "1.0.0")
+    ]);
+
+    pubDowngrade(args: ['bar']);
+
+    d.packagesDir({
+      "foo": "1.0.0",
+      "bar": "1.0.0"
+    }).validate();
+  });
+}
diff --git a/test/downgrade/unlock_if_necessary_test.dart b/test/downgrade/unlock_if_necessary_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..48c7bd86fe8176cbf83ff20ee7aa29fe029eada5
--- /dev/null
+++ b/test/downgrade/unlock_if_necessary_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2014, 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.
+
+library pub_tests;
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration("downgrades one locked hosted package's dependencies if it's "
+      "necessary", () {
+    servePackages([
+      packageMap("foo", "2.0.0", {"foo_dep": "any"}),
+      packageMap("foo_dep", "2.0.0")
+    ]);
+
+    d.appDir({"foo": "any"}).create();
+
+    pubGet();
+
+    d.packagesDir({
+      "foo": "2.0.0",
+      "foo_dep": "2.0.0"
+    }).validate();
+
+    servePackages([
+      packageMap("foo", "1.0.0", {"foo_dep": "<2.0.0"}),
+      packageMap("foo_dep", "1.0.0")
+    ]);
+
+    pubDowngrade(args: ['foo']);
+
+    d.packagesDir({
+      "foo": "1.0.0",
+      "foo_dep": "1.0.0"
+    }).validate();
+  });
+}
diff --git a/test/pub_test.dart b/test/pub_test.dart
index ae6ad21e97ee46d3de765d7b4c8624475232f99c..525cd0fd0ecea58aac8262176e5805bbb286e642 100644
--- a/test/pub_test.dart
+++ b/test/pub_test.dart
@@ -28,18 +28,19 @@ final USAGE_STRING = """
     -v, --verbose         Shortcut for "--verbosity=all".
 
     Available commands:
-      build      Apply transformers to build a package.
-      cache      Work with the system cache.
-      deps       Print package dependencies.
-      get        Get the current package's dependencies.
-      global     Work with global packages.
-      help       Display help information for Pub.
-      publish    Publish the current package to pub.dartlang.org.
-      run        Run an executable from a package.
-      serve      Run a local web development server.
-      upgrade    Upgrade the current package's dependencies to latest versions.
-      uploader   Manage uploaders for a package on pub.dartlang.org.
-      version    Print pub version.
+      build       Apply transformers to build a package.
+      cache       Work with the system cache.
+      deps        Print package dependencies.
+      downgrade   Downgrade the current package's dependencies to oldest versions.
+      get         Get the current package's dependencies.
+      global      Work with global packages.
+      help        Display help information for Pub.
+      publish     Publish the current package to pub.dartlang.org.
+      run         Run an executable from a package.
+      serve       Run a local web development server.
+      upgrade     Upgrade the current package's dependencies to latest versions.
+      uploader    Manage uploaders for a package on pub.dartlang.org.
+      version     Print pub version.
 
     Run "pub help [command]" for more information about a command.
     See http://dartlang.org/tools/pub for detailed documentation.
@@ -127,18 +128,19 @@ main() {
         Could not find a command named "quylthulg".
 
         Available commands:
-          build      Apply transformers to build a package.
-          cache      Work with the system cache.
-          deps       Print package dependencies.
-          get        Get the current package's dependencies.
-          global     Work with global packages.
-          help       Display help information for Pub.
-          publish    Publish the current package to pub.dartlang.org.
-          run        Run an executable from a package.
-          serve      Run a local web development server.
-          upgrade    Upgrade the current package's dependencies to latest versions.
-          uploader   Manage uploaders for a package on pub.dartlang.org.
-          version    Print pub version.
+          build       Apply transformers to build a package.
+          cache       Work with the system cache.
+          deps        Print package dependencies.
+          downgrade   Downgrade the current package's dependencies to oldest versions.
+          get         Get the current package's dependencies.
+          global      Work with global packages.
+          help        Display help information for Pub.
+          publish     Publish the current package to pub.dartlang.org.
+          run         Run an executable from a package.
+          serve       Run a local web development server.
+          upgrade     Upgrade the current package's dependencies to latest versions.
+          uploader    Manage uploaders for a package on pub.dartlang.org.
+          version     Print pub version.
         ''',
         exitCode: exit_codes.USAGE);
   });
@@ -297,18 +299,19 @@ main() {
             Could not find a command named "quylthulg".
 
             Available commands:
-              build      Apply transformers to build a package.
-              cache      Work with the system cache.
-              deps       Print package dependencies.
-              get        Get the current package's dependencies.
-              global     Work with global packages.
-              help       Display help information for Pub.
-              publish    Publish the current package to pub.dartlang.org.
-              run        Run an executable from a package.
-              serve      Run a local web development server.
-              upgrade    Upgrade the current package's dependencies to latest versions.
-              uploader   Manage uploaders for a package on pub.dartlang.org.
-              version    Print pub version.
+              build       Apply transformers to build a package.
+              cache       Work with the system cache.
+              deps        Print package dependencies.
+              downgrade   Downgrade the current package's dependencies to oldest versions.
+              get         Get the current package's dependencies.
+              global      Work with global packages.
+              help        Display help information for Pub.
+              publish     Publish the current package to pub.dartlang.org.
+              run         Run an executable from a package.
+              serve       Run a local web development server.
+              upgrade     Upgrade the current package's dependencies to latest versions.
+              uploader    Manage uploaders for a package on pub.dartlang.org.
+              version     Print pub version.
             ''',
             exitCode: exit_codes.USAGE);
     });
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 0650075cd3ec9ec029f4c0e7630cc8a75bcd8ef1..1bbf30716558b80ec23a70b3ae1d5ec1a4d758f2 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -329,6 +329,8 @@ class RunCommand {
       r'Got dependencies!|Changed \d+ dependenc(y|ies)!'));
   static final upgrade = new RunCommand('upgrade', new RegExp(
       r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
+  static final downgrade = new RunCommand('downgrade', new RegExp(
+      r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
 
   final String name;
   final RegExp success;
@@ -388,6 +390,12 @@ void pubUpgrade({Iterable<String> args, output, error, warning, int exitCode}) {
       warning: warning, exitCode: exitCode);
 }
 
+void pubDowngrade({Iterable<String> args, output, error, warning,
+    int exitCode}) {
+  pubCommand(RunCommand.downgrade, args: args, output: output, error: error,
+      warning: warning, exitCode: exitCode);
+}
+
 /// Schedules starting the "pub [global] run" process and validates the
 /// expected startup output.
 ///
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index b5302cb7e498c7d0b9642f53c65b9274ee5450e8..000ea2bcef5095b233ef5a52cb8d56b366892682 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -43,6 +43,7 @@ main() {
   group('SDK constraint', sdkConstraint);
   group('pre-release', prerelease);
   group('override', override);
+  group('downgrade', downgrade);
 }
 
 void basicGraph() {
@@ -1057,25 +1058,58 @@ void override() {
   });
 }
 
+void downgrade() {
+  testResolve("downgrades a dependency to the lowest matching version", {
+    'myapp 0.0.0': {
+      'foo': '>=2.0.0 <3.0.0'
+    },
+    'foo 1.0.0': {},
+    'foo 2.0.0-dev': {},
+    'foo 2.0.0': {},
+    'foo 2.1.0': {}
+  }, lockfile: {
+    'foo': '2.1.0'
+  }, result: {
+    'myapp from root': '0.0.0',
+    'foo': '2.0.0'
+  }, downgrade: true);
+
+  testResolve('use earliest allowed prerelease if no stable versions match '
+      'while downgrading', {
+    'myapp 0.0.0': {
+      'a': '>=2.0.0-dev.1 <3.0.0'
+    },
+    'a 1.0.0': {},
+    'a 2.0.0-dev.1': {},
+    'a 2.0.0-dev.2': {},
+    'a 2.0.0-dev.3': {}
+  }, result: {
+    'myapp from root': '0.0.0',
+    'a': '2.0.0-dev.1'
+  }, downgrade: true);
+}
+
 testResolve(String description, Map packages, {
     Map lockfile, Map overrides, Map result, FailMatcherBuilder error,
-    int maxTries}) {
+    int maxTries, bool downgrade: false}) {
   _testResolve(test, description, packages, lockfile: lockfile,
-      overrides: overrides, result: result, error: error, maxTries: maxTries);
+      overrides: overrides, result: result, error: error, maxTries: maxTries,
+      downgrade: downgrade);
 }
 
 solo_testResolve(String description, Map packages, {
     Map lockfile, Map overrides, Map result, FailMatcherBuilder error,
-    int maxTries}) {
+    int maxTries, bool downgrade: false}) {
   log.verbosity = log.Verbosity.SOLVER;
   _testResolve(solo_test, description, packages, lockfile: lockfile,
-      overrides: overrides, result: result, error: error, maxTries: maxTries);
+      overrides: overrides, result: result, error: error, maxTries: maxTries,
+      downgrade: downgrade);
 }
 
 _testResolve(void testFn(String description, Function body),
     String description, Map packages, {
     Map lockfile, Map overrides, Map result, FailMatcherBuilder error,
-    int maxTries}) {
+    int maxTries, bool downgrade: false}) {
   if (maxTries == null) maxTries = 1;
 
   testFn(description, () {
@@ -1124,7 +1158,9 @@ _testResolve(void testFn(String description, Function body),
     }
 
     // Resolve the versions.
-    var future = resolveVersions(cache.sources, root, lockFile: realLockFile);
+    var future = resolveVersions(
+        downgrade ? SolveType.DOWNGRADE : SolveType.GET,
+        cache.sources, root, lockFile: realLockFile);
 
     var matcher;
     if (result != null) {
@@ -1327,6 +1363,7 @@ class MockSource extends CachedSource {
   final _requestedPubspecs = new Map<String, Set<Version>>();
 
   final String name;
+  final hasMultipleVersions = true;
 
   MockSource(this.name);