diff --git a/bin/pub.dart b/bin/pub.dart
index ab95fc6ecf0ccf45d3a42eb5ff68f67d6f32f997..35b8c90ef63e1d6a326d49c14ea34cd61e71c93b 100644
--- a/bin/pub.dart
+++ b/bin/pub.dart
@@ -173,7 +173,7 @@ Future invokeCommand(String cacheDir, ArgResults mainOptions) {
   }
 
   return syncFuture(() {
-    return command.run(cacheDir, options);
+    return command.run(cacheDir, mainOptions, options);
   }).whenComplete(() {
     command.cache.deleteTempDir();
   });
diff --git a/lib/src/command.dart b/lib/src/command.dart
index 0b3e985bb2ba83de6dedacc646630903088e1367..d70bf1733ef3ed52ef1c563ae8af384e948be04a 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -105,6 +105,10 @@ abstract class PubCommand {
   GlobalPackages get globals => _globals;
   GlobalPackages _globals;
 
+  /// The parsed options for the pub executable.
+  ArgResults get globalOptions => _globalOptions;
+  ArgResults _globalOptions;
+
   /// The parsed options for this command.
   ArgResults get commandOptions => _commandOptions;
   ArgResults _commandOptions;
@@ -115,7 +119,10 @@ abstract class PubCommand {
   /// is not a package.
   Entrypoint get entrypoint {
     // Lazy load it.
-    if (_entrypoint == null) _entrypoint = new Entrypoint(path.current, _cache);
+    if (_entrypoint == null) {
+      _entrypoint = new Entrypoint(path.current, _cache,
+          packageSymlinks: globalOptions['package-symlinks']);
+    }
     return _entrypoint;
   }
 
@@ -181,8 +188,10 @@ abstract class PubCommand {
         help: 'Print usage information for this command.');
   }
 
-  /// Runs this command using a system cache at [cacheDir] with [options].
-  Future run(String cacheDir, ArgResults options) {
+  /// Runs this command using a system cache at [cacheDir] with [globalOptions]
+  /// and [options].
+  Future run(String cacheDir, ArgResults globalOptions, ArgResults options) {
+    _globalOptions = globalOptions;
     _commandOptions = options;
 
     _cache = new SystemCache.withSources(cacheDir, isOffline: isOffline);
@@ -305,6 +314,8 @@ ArgParser _initArgParser() {
       help: 'Shortcut for "--verbosity=all".');
   argParser.addFlag('with-prejudice', hide: !isAprilFools, negatable: false,
       help: 'Execute commands with prejudice.');
+  argParser.addFlag('package-symlinks', hide: true, negatable: true,
+      defaultsTo: true);
 
   // Register the commands.
   PubCommand.mainCommands.forEach((name, command) {
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 6a9f6076bdb37ed5f7c1348b9fde2b850715ea08..6ce137d954bd6d38636fca3e9fc6ee178e174c8e 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -43,18 +43,28 @@ class Entrypoint {
   /// the network.
   final SystemCache cache;
 
+  /// Whether to create and symlink a "packages" directory containing links to
+  /// the installed packages.
+  final bool _packageSymlinks;
+
   /// The lockfile for the entrypoint.
   ///
   /// If not provided to the entrypoint, it will be laoded lazily from disc.
   LockFile _lockFile;
 
   /// Loads the entrypoint from a package at [rootDir].
-  Entrypoint(String rootDir, SystemCache cache)
+  ///
+  /// If [packageSymlinks] is `true`, this will create a "packages" directory
+  /// with symlinks to the installed packages. This directory will be symlinked
+  /// into any directory that might contain an entrypoint.
+  Entrypoint(String rootDir, SystemCache cache, {bool packageSymlinks: true})
       : root = new Package.load(null, rootDir, cache.sources),
-        cache = cache;
+        cache = cache,
+        _packageSymlinks = packageSymlinks;
 
   /// Creates an entrypoint given package and lockfile objects.
-  Entrypoint.inMemory(this.root, this._lockFile, this.cache);
+  Entrypoint.inMemory(this.root, this._lockFile, this.cache)
+      : _packageSymlinks = false;
 
   /// The path to the entrypoint's "packages" directory.
   String get packagesDir => path.join(root.dir, 'packages');
@@ -109,12 +119,19 @@ class Entrypoint {
         return null;
       }
 
-      // Install the packages.
-      cleanDir(packagesDir);
+      // Install the packages and maybe link them into the entrypoint.
+      if (_packageSymlinks) {
+        cleanDir(packagesDir);
+      } else {
+        deleteEntry(packagesDir);
+      }
+
       return Future.wait(result.packages.map(_get)).then((ids) {
         _saveLockFile(ids);
-        _linkSelf();
-        _linkSecondaryPackageDirs();
+
+        if (_packageSymlinks) _linkSelf();
+        _linkOrDeleteSecondaryPackageDirs();
+
         result.summarizeChanges(type, dryRun: dryRun);
       });
     });
@@ -128,11 +145,17 @@ class Entrypoint {
   Future<PackageId> _get(PackageId id) {
     if (id.isRoot) return new Future.value(id);
 
-    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));
+    return syncFuture(() {
+      if (!_packageSymlinks) {
+        if (source is! CachedSource) return null;
+        return source.downloadToSystemCache(id);
+      }
+
+      var packageDir = path.join(packagesDir, id.name);
+      if (entryExists(packageDir)) deleteEntry(packageDir);
+      return source.get(id, packageDir);
+    }).then((_) => source.resolveId(id));
   }
 
   /// Determines whether or not the lockfile is out of date with respect to the
@@ -257,28 +280,34 @@ class Entrypoint {
         isSelfLink: true, relative: true);
   }
 
-  /// Add "packages" directories to the whitelist of directories that may
-  /// contain Dart entrypoints.
-  void _linkSecondaryPackageDirs() {
+  /// If [packageSymlinks] is true, add "packages" directories to the whitelist
+  /// of directories that may contain Dart entrypoints.
+  ///
+  /// Otherwise, delete any "packages" directories in the whitelist of
+  /// directories that may contain Dart entrypoints.
+  void _linkOrDeleteSecondaryPackageDirs() {
     // Only the main "bin" directory gets a "packages" directory, not its
     // subdirectories.
     var binDir = path.join(root.dir, 'bin');
-    if (dirExists(binDir)) _linkSecondaryPackageDir(binDir);
+    if (dirExists(binDir)) _linkOrDeleteSecondaryPackageDir(binDir);
 
     // The others get "packages" directories in subdirectories too.
     for (var dir in ['benchmark', 'example', 'test', 'tool', 'web']) {
-      _linkSecondaryPackageDirsRecursively(path.join(root.dir, dir));
+      _linkOrDeleteSecondaryPackageDirsRecursively(path.join(root.dir, dir));
     }
  }
 
-  /// Creates a symlink to the `packages` directory in [dir] and all its
+  /// If [packageSymlinks] is true, creates a symlink to the "packages"
+  /// directory in [dir] and all its subdirectories.
+  ///
+  /// Otherwise, deletes any "packages" directories in [dir] and all its
   /// subdirectories.
-  void _linkSecondaryPackageDirsRecursively(String dir) {
+  void _linkOrDeleteSecondaryPackageDirsRecursively(String dir) {
     if (!dirExists(dir)) return;
-    _linkSecondaryPackageDir(dir);
+    _linkOrDeleteSecondaryPackageDir(dir);
     _listDirWithoutPackages(dir)
         .where(dirExists)
-        .forEach(_linkSecondaryPackageDir);
+        .forEach(_linkOrDeleteSecondaryPackageDir);
   }
 
   // TODO(nweiz): roll this into [listDir] in io.dart once issue 4775 is fixed.
@@ -294,11 +323,13 @@ class Entrypoint {
     }));
   }
 
-  /// Creates a symlink to the `packages` directory in [dir]. Will replace one
-  /// if already there.
-  void _linkSecondaryPackageDir(String dir) {
+  /// If [packageSymlinks] is true, creates a symlink to the "packages"
+  /// directory in [dir].
+  ///
+  /// Otherwise, deletes a "packages" directories in [dir] if one exists.
+  void _linkOrDeleteSecondaryPackageDir(String dir) {
     var symlink = path.join(dir, 'packages');
     if (entryExists(symlink)) deleteEntry(symlink);
-    createSymlink(packagesDir, symlink, relative: true);
+    if (_packageSymlinks) createSymlink(packagesDir, symlink, relative: true);
   }
 }
diff --git a/test/no_package_symlinks_test.dart b/test/no_package_symlinks_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..12d51514569244f30441556ba3b903dea8f8419e
--- /dev/null
+++ b/test/no_package_symlinks_test.dart
@@ -0,0 +1,115 @@
+// 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:scheduled_test/scheduled_test.dart';
+
+import 'descriptor.dart' as d;
+import 'test_pub.dart';
+
+main() {
+  initConfig();
+
+  forBothPubGetAndUpgrade((command) {
+    group("with --no-package-symlinks", () {
+      integration("installs hosted dependencies to the cache", () {
+        servePackages([
+          packageMap("foo", "1.0.0"),
+          packageMap("bar", "1.0.0")
+        ]);
+
+        d.appDir({"foo": "any", "bar": "any"}).create();
+
+        pubCommand(command, args: ["--no-package-symlinks"]);
+
+        d.nothing("$appPath/packages").validate();
+
+        d.hostedCache([
+          d.dir("foo-1.0.0", [
+            d.dir("lib", [d.file("foo.dart", 'main() => "foo 1.0.0";')])
+          ]),
+          d.dir("bar-1.0.0", [
+            d.dir("lib", [d.file("bar.dart", 'main() => "bar 1.0.0";')])
+          ])
+        ]).validate();
+      });
+
+      integration("installs git dependencies to the cache", () {
+        ensureGit();
+
+        d.git('foo.git', [
+          d.libDir('foo'),
+          d.libPubspec('foo', '1.0.0')
+        ]).create();
+
+        d.appDir({"foo": {"git": "../foo.git"}}).create();
+
+        pubCommand(command, args: ["--no-package-symlinks"]);
+
+        d.nothing("$appPath/packages").validate();
+
+        d.dir(cachePath, [
+          d.dir('git', [
+            d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
+            d.gitPackageRevisionCacheDir('foo')
+          ])
+        ]).validate();
+      });
+
+      integration("locks path dependencies", () {
+        d.dir("foo", [
+          d.libDir("foo"),
+          d.libPubspec("foo", "0.0.1")
+        ]).create();
+
+        d.dir(appPath, [
+          d.appPubspec({
+            "foo": {"path": "../foo"}
+          })
+        ]).create();
+
+        pubCommand(command, args: ["--no-package-symlinks"]);
+
+        d.nothing("$appPath/packages").validate();
+        d.matcherFile("$appPath/pubspec.lock", contains("foo"));
+      });
+
+      integration("removes package directories near entrypoints", () {
+        d.dir(appPath, [
+          d.appPubspec(),
+          d.dir("packages"),
+          d.dir("bin/packages"),
+          d.dir("web/packages"),
+          d.dir("web/subdir/packages")
+        ]).create();
+
+        pubCommand(command, args: ["--no-package-symlinks"]);
+
+        d.dir(appPath, [
+          d.nothing("packages"),
+          d.nothing("bin/packages"),
+          d.nothing("web/packages"),
+          d.nothing("web/subdir/packages")
+        ]).validate();
+      });
+
+      integration("doesn't remove package directories that pub wouldn't "
+          "generate", () {
+        d.dir(appPath, [
+          d.appPubspec(),
+          d.dir("packages"),
+          d.dir("bin/subdir/packages"),
+          d.dir("lib/packages")
+        ]).create();
+
+        pubCommand(command, args: ["--no-package-symlinks"]);
+
+        d.dir(appPath, [
+          d.nothing("packages"),
+          d.dir("bin/subdir/packages"),
+          d.dir("lib/packages")
+        ]).validate();
+      });
+    });
+  });
+}