diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index a2c669b13ce5656b6f0f27061cbe68bdd7556f89..3eeeebd1d43d8127bb83102eae169d39e064eec3 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -65,41 +65,6 @@ class Entrypoint {
   /// The path to the entrypoint package's lockfile.
   String get lockFilePath => path.join(root.dir, 'pubspec.lock');
 
-  /// Gets package [id] and makes it available for use by this entrypoint.
-  ///
-  /// If this completes successfully, the package is guaranteed to be importable
-  /// using the `package:` scheme. Returns the resolved [PackageId].
-  ///
-  /// This automatically downloads the package to the system-wide cache as well
-  /// if it requires network access to retrieve (specifically, if the package's
-  /// source is a [CachedSource]).
-  ///
-  /// See also [getDependencies].
-  Future<PackageId> get(PackageId id) {
-    var pending = _pendingGets[id];
-    if (pending != null) return pending;
-
-    var packageDir = path.join(packagesDir, id.name);
-
-    var future = syncFuture(() {
-      ensureDir(path.dirname(packageDir));
-
-      if (entryExists(packageDir)) {
-        // TODO(nweiz): figure out when to actually delete the directory, and
-        // when we can just re-use the existing symlink.
-        log.fine("Deleting package directory for ${id.name} before get.");
-        deleteEntry(packageDir);
-      }
-
-      var source = cache.sources[id.source];
-      return source.get(id, packageDir).then((_) => source.resolveId(id));
-    });
-
-    _pendingGets[id] = future;
-
-    return future;
-  }
-
   /// Gets all dependencies of the [root] package.
   ///
   /// [useLatest], if provided, defines a list of packages that will be
@@ -129,10 +94,7 @@ class Entrypoint {
 
       // Install the packages.
       cleanDir(packagesDir);
-      return Future.wait(result.packages.map((id) {
-        if (id.isRoot) return new Future.value(id);
-        return get(id);
-      }).toList()).then((ids) {
+      return Future.wait(result.packages.map(_get).toList()).then((ids) {
         _saveLockFile(ids);
         _linkSelf();
         _linkSecondaryPackageDirs();
@@ -141,6 +103,30 @@ class Entrypoint {
     });
   }
 
+  /// Makes sure the package at [id] is locally available.
+  ///
+  /// This automatically downloads the package to the system-wide cache as well
+  /// if it requires network access to retrieve (specifically, if the package's
+  /// source is a [CachedSource]).
+  Future<PackageId> _get(PackageId id) {
+    if (id.isRoot) return new Future.value(id);
+
+    var pending = _pendingGets[id];
+    if (pending != null) return pending;
+
+    var future = syncFuture(() {
+      var packageDir = path.join(packagesDir, id.name);
+      if (entryExists(packageDir)) deleteEntry(packageDir);
+
+      var source = cache.sources[id.source];
+      return source.get(id, packageDir).then((_) => source.resolveId(id));
+    });
+
+    _pendingGets[id] = future;
+
+    return future;
+  }
+
   /// Loads the list of concrete package versions from the `pubspec.lock`, if it
   /// exists.
   ///
diff --git a/lib/src/source.dart b/lib/src/source.dart
index 8cfec0ff8d7bf4caa8aa4bb7660e95cfe02b0670..823b86023d7df38b36bf2a623661670137d8da3f 100644
--- a/lib/src/source.dart
+++ b/lib/src/source.dart
@@ -94,11 +94,17 @@ abstract class Source {
   /// external code should not call this. Instead, call [describe].
   Future<Pubspec> doDescribe(PackageId id);
 
-  /// Gets the package identified by [id] and places it at [path].
+  /// Ensures that the package identified by [id] is present on the local file
+  /// system.
   ///
-  /// Returns a [Future] that completes when the operation finishes. [path] is
-  /// guaranteed not to exist, and its parent directory is guaranteed to exist.
-  Future get(PackageId id, String path);
+  /// For cached sources, this ensures the package is in the system cache. (If
+  /// already cached, it does nothing.) For uncached sources, it does nothing
+  /// since the package is already local.
+  Future ensureLocal(PackageId id);
+
+  /// Ensures [id] is available locally and creates a symlink at [symlink]
+  /// pointing it.
+  Future get(PackageId id, String symlink);
 
   /// Returns the directory where this package can (or could) be found locally.
   ///
diff --git a/lib/src/source/cached.dart b/lib/src/source/cached.dart
index 4fada96e6bb3920f6beaa30ecab62da57f2a87a8..aaafd3190a1122eff406c2a88a3081d8a67ff0a1 100644
--- a/lib/src/source/cached.dart
+++ b/lib/src/source/cached.dart
@@ -47,9 +47,14 @@ abstract class CachedSource extends Source {
   /// the system cache.
   Future<Pubspec> describeUncached(PackageId id);
 
-  Future get(PackageId id, String packageDir) {
-    return downloadToSystemCache(id).then(
-        (pkg) => createPackageSymlink(id.name, pkg.dir, packageDir));
+  Future ensureLocal(PackageId id) {
+    return downloadToSystemCache(id);
+  }
+
+  Future get(PackageId id, String symlink) {
+    return downloadToSystemCache(id).then((pkg) {
+      createPackageSymlink(id.name, pkg.dir, symlink);
+    });
   }
 
   /// Determines if the package with [id] is already downloaded to the system
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index c0aa95d668ce626d4b89d8fd206e79c2c232615c..510407a3e61af722904be44ba7e746b746361044 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -24,8 +24,6 @@ class GitSource extends CachedSource {
   /// has already been run during this run of pub.
   final _updatedRepos = new Set<String>();
 
-  GitSource();
-
   /// Since we don't have an easy way to read from a remote Git repo, this
   /// just installs [id] into the system cache, then describes it from there.
   Future<Pubspec> describeUncached(PackageId id) {
@@ -44,11 +42,7 @@ class GitSource extends CachedSource {
   /// `<package name>-<url hash>`. These are used to check out the repository
   /// itself; each of the commit-specific directories are clones of a directory
   /// in `cache/`.
-  Future<Package> downloadToSystemCache(PackageId id, {bool force}) {
-    // Force is not supported because the cache repair command doesn't need it.
-    // Instead, it uses [resetCachedPackages].
-    assert(force != true);
-
+  Future<Package> downloadToSystemCache(PackageId id) {
     var revisionCachePath;
 
     if (!git.isInstalled) {
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index 4a92d770b7485159d8a00b082a32ba8089dcee37..808742ba5e558df836ebe4987e65d628d0d1c909 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -33,12 +33,13 @@ class PathSource extends Source {
     return path1 == path2;
   }
 
-  /// Create a symlink from the source path directly to the destination
-  /// directory.
-  Future get(PackageId id, String destination) {
+  /// Path dependencies are already local.
+  Future ensureLocal(PackageId id) => new Future.value();
+
+  Future get(PackageId id, String symlink) {
     return syncFuture(() {
       var dir = _validatePath(id.name, id.description);
-      createPackageSymlink(id.name, dir, destination,
+      createPackageSymlink(id.name, dir, symlink,
           relative: id.description["relative"]);
     });
   }
diff --git a/lib/src/source/unknown.dart b/lib/src/source/unknown.dart
index 5fcd27389281d395414d9507d6b463c16edbbdc8..da20dc2bc5a8e05576da9202a58de7ba34a15cf2 100644
--- a/lib/src/source/unknown.dart
+++ b/lib/src/source/unknown.dart
@@ -31,9 +31,12 @@ class UnknownSource extends Source {
   Future<Pubspec> doDescribe(PackageId id) => throw new UnsupportedError(
       "Cannot describe a package from unknown source '$name'.");
 
-  Future<bool> get(PackageId id, String path) => throw new UnsupportedError(
+  Future ensureLocal(PackageId id) => throw new UnsupportedError(
       "Cannot get a package from an unknown source '$name'.");
 
+  Future get(PackageId id, String symlink) => throw new UnsupportedError(
+      "Cannot get an unknown source '$name'.");
+
   /// Returns the directory where this package can be found locally.
   Future<String> getDirectory(PackageId id) => throw new UnsupportedError(
       "Cannot find a package from an unknown source '$name'.");
diff --git a/test/lock_file_test.dart b/test/lock_file_test.dart
index 04430ab65e47b6c3d9eb7f1da2b65779d558935a..85724e46ffdff0725973414e5eec90d5fcaccacc 100644
--- a/test/lock_file_test.dart
+++ b/test/lock_file_test.dart
@@ -23,9 +23,12 @@ class MockSource extends Source {
   Future<Pubspec> doDescribe(PackageId id) => throw new UnsupportedError(
       "Cannot describe mock packages.");
 
-  Future<bool> get(PackageId id, String path) => throw new UnsupportedError(
+  Future ensureLocal(PackageId id) => throw new UnsupportedError(
       "Cannot get a mock package.");
 
+  Future get(PackageId id, String symlink) => throw new UnsupportedError(
+      "Cannot get an mock.");
+
   Future<String> getDirectory(PackageId id) => throw new UnsupportedError(
       "Cannot get the directory for mock packages.");
 
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index b4361a3efbe532d8a41b1c1690597f9ba0948c50..29c133ac4d823c9888a98acf08d2f2e38b7fe9f1 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -21,7 +21,10 @@ class MockSource extends Source {
   Future<Pubspec> doDescribe(PackageId id) => throw new UnsupportedError(
       "Cannot describe mock packages.");
 
-  Future<bool> get(PackageId id, String path) => throw new UnsupportedError(
+  Future ensureLocal(PackageId id) => throw new UnsupportedError(
+      "Cannot get a mock package.");
+
+  Future get(PackageId id, String symlink) => throw new UnsupportedError(
       "Cannot get a mock package.");
 
   Future<String> getDirectory(PackageId id) => throw new UnsupportedError(