From 45a68affb075da95b8f75ea667412c304f909e92 Mon Sep 17 00:00:00 2001 From: "rnystrom@google.com" <rnystrom@google.com> Date: Fri, 15 Aug 2014 00:05:55 +0000 Subject: [PATCH] Support Git in pub global activate. BUG=https://code.google.com/p/dart/issues/detail?id=19902 R=nweiz@google.com Review URL: https://codereview.chromium.org//448933002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge@39279 260f80e4-7a28-3924-810f-c04153c831b5 --- lib/src/command/global_activate.dart | 15 +++++-- lib/src/global_packages.dart | 33 ++++++++++++-- lib/src/source/git.dart | 40 +++++++++++++---- .../activate_git_after_hosted_test.dart | 41 ++++++++++++++++++ .../activate_hosted_after_git_test.dart | 43 +++++++++++++++++++ test/global/activate/bad_version_test.dart | 2 +- .../activate/constraint_with_path_test.dart | 2 +- test/global/activate/git_package_test.dart | 25 +++++++++++ .../installs_dependencies_for_git_test.dart | 33 ++++++++++++++ .../activate/installs_dependencies_test.dart | 2 +- .../activate/missing_git_repo_test.dart | 18 ++++++++ .../activate/missing_package_arg_test.dart | 2 +- .../reactivating_git_upgrades_test.dart | 34 +++++++++++++++ .../activate/unexpected_arguments_test.dart | 2 +- test/global/deactivate/git_package_test.dart | 25 +++++++++++ test/global/run/runs_git_script_test.dart | 26 +++++++++++ 16 files changed, 322 insertions(+), 21 deletions(-) create mode 100644 test/global/activate/activate_git_after_hosted_test.dart create mode 100644 test/global/activate/activate_hosted_after_git_test.dart create mode 100644 test/global/activate/git_package_test.dart create mode 100644 test/global/activate/installs_dependencies_for_git_test.dart create mode 100644 test/global/activate/missing_git_repo_test.dart create mode 100644 test/global/activate/reactivating_git_upgrades_test.dart create mode 100644 test/global/deactivate/git_package_test.dart create mode 100644 test/global/run/runs_git_script_test.dart diff --git a/lib/src/command/global_activate.dart b/lib/src/command/global_activate.dart index 98ba40e5..8448af58 100644 --- a/lib/src/command/global_activate.dart +++ b/lib/src/command/global_activate.dart @@ -20,7 +20,7 @@ class GlobalActivateCommand extends PubCommand { commandParser.addOption("source", abbr: "s", help: "The source used to find the package.", - allowed: ["hosted", "path"], + allowed: ["git", "hosted", "path"], defaultsTo: "hosted"); } @@ -41,10 +41,16 @@ class GlobalActivateCommand extends PubCommand { usageError("Unexpected $arguments ${toSentence(unexpected)}."); } - var package = readArg("No package to activate given."); - switch (commandOptions["source"]) { + case "git": + var repo = readArg("No Git repository given."); + // TODO(rnystrom): Allow passing in a Git ref too. + validateNoExtraArgs(); + return globals.activateGit(repo); + case "hosted": + var package = readArg("No package to activate given."); + // Parse the version constraint, if there is one. var constraint = VersionConstraint.any; if (args.isNotEmpty) { @@ -59,8 +65,9 @@ class GlobalActivateCommand extends PubCommand { return globals.activateHosted(package, constraint); case "path": + var path = readArg("No package to activate given."); validateNoExtraArgs(); - return globals.activatePath(package); + return globals.activatePath(path); } throw "unreachable"; diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart index 0baa6502..f2828ecf 100644 --- a/lib/src/global_packages.dart +++ b/lib/src/global_packages.dart @@ -16,8 +16,8 @@ import 'log.dart' as log; import 'package.dart'; import 'system_cache.dart'; import 'solver/version_solver.dart'; -import 'source.dart'; import 'source/cached.dart'; +import 'source/git.dart'; import 'source/path.dart'; import 'utils.dart'; import 'version.dart'; @@ -60,6 +60,19 @@ class GlobalPackages { /// when needed. GlobalPackages(this.cache); + /// Caches the package located in the Git repository [repo] and makes it the + /// active global version. + Future activateGit(String repo) { + var source = cache.sources["git"] as GitSource; + return source.getPackageNameFromRepo(repo).then((name) { + // Call this just to log what the current active package is, if any. + _describeActive(name); + + var id = new PackageId(name, "git", Version.none, repo); + return _installInCache(id); + }); + } + /// Finds the latest version of the hosted package with [name] that matches /// [constraint] and makes it the active global version. Future activateHosted(String name, VersionConstraint constraint) { @@ -155,7 +168,11 @@ class GlobalPackages { writeTextFile(_getLockFilePath(id.name), lockFile.serialize(cache.rootDir, cache.sources)); - if (id.source == "path") { + if (id.source == "git") { + var url = GitSource.urlFromDescription(id.description); + log.message('Activated ${log.bold(id.name)} ${id.version} from Git ' + 'repository "$url".'); + } else if (id.source == "path") { var path = PathSource.pathFromDescription(id.description); log.message('Activated ${log.bold(id.name)} ${id.version} at path ' '"$path".'); @@ -174,7 +191,11 @@ class GlobalPackages { cache.sources); var id = lockFile.packages[package]; - if (id.source == "path") { + if (id.source == "git") { + var url = GitSource.urlFromDescription(id.description); + log.message('Package ${log.bold(id.name)} is currently active from ' + 'Git repository "${url}".'); + } else if (id.source == "path") { var path = PathSource.pathFromDescription(id.description); log.message('Package ${log.bold(package)} is currently active at ' 'path "$path".'); @@ -204,7 +225,11 @@ class GlobalPackages { deleteEntry(lockFilePath); if (logDeactivate) { - if (id.source == "path") { + if (id.source == "git") { + var url = GitSource.urlFromDescription(id.description); + log.message('Deactivated package ${log.bold(name)} from Git repository ' + '"$url".'); + } else if (id.source == "path") { var path = PathSource.pathFromDescription(id.description); log.message('Deactivated package ${log.bold(name)} at path "$path".'); } else { diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart index 9f4c38c3..9696f45b 100644 --- a/lib/src/source/git.dart +++ b/lib/src/source/git.dart @@ -18,12 +18,28 @@ import 'cached.dart'; /// A package source that gets packages from Git repos. class GitSource extends CachedSource { + /// Given a valid git package description, returns the URL of the repository + /// it pulls from. + static String urlFromDescription(description) => description["url"]; + final name = "git"; /// The paths to the canonical clones of repositories for which "git fetch" /// has already been run during this run of pub. final _updatedRepos = new Set<String>(); + /// Given a Git repo that contains a pub package, gets the name of the pub + /// package. + Future<String> getPackageNameFromRepo(String repo) { + // Clone the repo to a temp directory. + return withTempDir((tempDir) { + return _clone(repo, tempDir, shallow: true).then((_) { + var pubspec = new Pubspec.load(tempDir, systemCache.sources); + return pubspec.name; + }); + }); + } + /// 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) { @@ -134,10 +150,10 @@ class GitSource extends CachedSource { var failures = 0; var packages = listDir(systemCacheRoot) - .where((entry) => dirExists(path.join(entry, ".git"))) - .map((packageDir) => new Package.load(null, packageDir, - systemCache.sources)) - .toList(); + .where((entry) => dirExists(path.join(entry, ".git"))) + .map((packageDir) => new Package.load(null, packageDir, + systemCache.sources)) + .toList(); // Note that there may be multiple packages with the same name and version // (pinned to different commits). The sort order of those is unspecified. @@ -219,16 +235,24 @@ class GitSource extends CachedSource { /// Clones the repo at the URI [from] to the path [to] on the local /// filesystem. /// - /// If [mirror] is true, create a bare, mirrored clone. This doesn't check out - /// the working tree, but instead makes the repository a local mirror of the - /// remote repository. See the manpage for `git clone` for more information. - Future _clone(String from, String to, {bool mirror: false}) { + /// If [mirror] is true, creates a bare, mirrored clone. This doesn't check + /// out the working tree, but instead makes the repository a local mirror of + /// the remote repository. See the manpage for `git clone` for more + /// information. + /// + /// If [shallow] is true, creates a shallow clone that contains no history + /// for the repository. + Future _clone(String from, String to, {bool mirror: false, + bool shallow: false}) { return syncFuture(() { // Git on Windows does not seem to automatically create the destination // directory. ensureDir(to); var args = ["clone", from, to]; + if (mirror) args.insert(1, "--mirror"); + if (shallow) args.insertAll(1, ["--depth", "1"]); + return git.run(args); }).then((result) => null); } diff --git a/test/global/activate/activate_git_after_hosted_test.dart b/test/global/activate/activate_git_after_hosted_test.dart new file mode 100644 index 00000000..a553f5f4 --- /dev/null +++ b/test/global/activate/activate_git_after_hosted_test.dart @@ -0,0 +1,41 @@ +// 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 '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('activating a Git package deactivates the hosted one', () { + ensureGit(); + + servePackages([ + packageMap("foo", "1.0.0") + ], contents: [ + d.dir("bin", [ + d.file("foo.dart", "main(args) => print('hosted');") + ]) + ]); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0"), + d.dir("bin", [ + d.file("foo.dart", "main() => print('git');") + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "foo"]); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"], + output: """ + Package foo is currently active at version 1.0.0. + Resolving dependencies... + Activated foo 1.0.0 from Git repository "../foo.git"."""); + + // Should now run the git one. + var pub = pubRun(global: true, args: ["foo"]); + pub.stdout.expect("git"); + pub.shouldExit(); + }); +} diff --git a/test/global/activate/activate_hosted_after_git_test.dart b/test/global/activate/activate_hosted_after_git_test.dart new file mode 100644 index 00000000..9afb49fd --- /dev/null +++ b/test/global/activate/activate_hosted_after_git_test.dart @@ -0,0 +1,43 @@ +// 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 p; + +import '../../../lib/src/io.dart'; +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('activating a hosted package deactivates the Git one', () { + servePackages([ + packageMap("foo", "2.0.0") + ], contents: [ + d.dir("bin", [ + d.file("foo.dart", "main(args) => print('hosted');") + ]) + ]); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0"), + d.dir("bin", [ + d.file("foo.dart", "main() => print('git');") + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"]); + + var path = canonicalize(p.join(sandboxDir, "foo")); + schedulePub(args: ["global", "activate", "foo"], output: """ + Package foo is currently active from Git repository "../foo.git". + Downloading foo 2.0.0... + Resolving dependencies... + Activated foo 2.0.0."""); + + // Should now run the hosted one. + var pub = pubRun(global: true, args: ["foo"]); + pub.stdout.expect("hosted"); + pub.shouldExit(); + }); +} diff --git a/test/global/activate/bad_version_test.dart b/test/global/activate/bad_version_test.dart index 245fe8b7..73cb2ae3 100644 --- a/test/global/activate/bad_version_test.dart +++ b/test/global/activate/bad_version_test.dart @@ -15,7 +15,7 @@ main() { Usage: pub global activate <package...> -h, --help Print usage information for this command. -s, --source The source used to find the package. - [hosted (default), path] + [git, hosted (default), path] Run "pub help" to see global options. """, diff --git a/test/global/activate/constraint_with_path_test.dart b/test/global/activate/constraint_with_path_test.dart index 68f2ac38..2795ca41 100644 --- a/test/global/activate/constraint_with_path_test.dart +++ b/test/global/activate/constraint_with_path_test.dart @@ -15,7 +15,7 @@ main() { Usage: pub global activate <package...> -h, --help Print usage information for this command. -s, --source The source used to find the package. - [hosted (default), path] + [git, hosted (default), path] Run "pub help" to see global options. """, diff --git a/test/global/activate/git_package_test.dart b/test/global/activate/git_package_test.dart new file mode 100644 index 00000000..4426c74c --- /dev/null +++ b/test/global/activate/git_package_test.dart @@ -0,0 +1,25 @@ +// 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 '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('activates a package from a Git repo', () { + ensureGit(); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0"), + d.dir("bin", [ + d.file("foo.dart", "main() => print('ok');") + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"], + output: ''' +Resolving dependencies... +Activated foo 1.0.0 from Git repository "../foo.git".'''); + }); +} diff --git a/test/global/activate/installs_dependencies_for_git_test.dart b/test/global/activate/installs_dependencies_for_git_test.dart new file mode 100644 index 00000000..b50c1f1f --- /dev/null +++ b/test/global/activate/installs_dependencies_for_git_test.dart @@ -0,0 +1,33 @@ +// 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(); + integration('activating a Git package installs its dependencies', () { + servePackages([ + packageMap("bar", "1.0.0", {"baz": "any"}), + packageMap("baz", "1.0.0") + ]); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0", deps: { + "bar": "any" + }), + d.dir("bin", [ + d.file("foo.dart", "main() => print('ok');") + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"], + output: allOf([ + contains("Downloading bar 1.0.0..."), + contains("Downloading baz 1.0.0...") + ])); + }); +} diff --git a/test/global/activate/installs_dependencies_test.dart b/test/global/activate/installs_dependencies_test.dart index d7726f65..7918bfdf 100644 --- a/test/global/activate/installs_dependencies_test.dart +++ b/test/global/activate/installs_dependencies_test.dart @@ -8,7 +8,7 @@ import '../../test_pub.dart'; main() { initConfig(); - integration('activating a package installs its dependencies too', () { + integration('activating a package installs its dependencies', () { servePackages([ packageMap("foo", "1.0.0", {"bar": "any"}), packageMap("bar", "1.0.0", {"baz": "any"}), diff --git a/test/global/activate/missing_git_repo_test.dart b/test/global/activate/missing_git_repo_test.dart new file mode 100644 index 00000000..6b840b8f --- /dev/null +++ b/test/global/activate/missing_git_repo_test.dart @@ -0,0 +1,18 @@ +// 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 '../../test_pub.dart'; + +main() { + initConfig(); + integration('fails if the Git repo does not exist', () { + ensureGit(); + + schedulePub(args: ["global", "activate", "-sgit", "../nope.git"], + error: contains("repository '../nope.git' does not exist"), + exitCode: 1); + }); +} diff --git a/test/global/activate/missing_package_arg_test.dart b/test/global/activate/missing_package_arg_test.dart index 54384b4c..9469de2d 100644 --- a/test/global/activate/missing_package_arg_test.dart +++ b/test/global/activate/missing_package_arg_test.dart @@ -15,7 +15,7 @@ main() { Usage: pub global activate <package...> -h, --help Print usage information for this command. -s, --source The source used to find the package. - [hosted (default), path] + [git, hosted (default), path] Run "pub help" to see global options.""", exitCode: exit_codes.USAGE); diff --git a/test/global/activate/reactivating_git_upgrades_test.dart b/test/global/activate/reactivating_git_upgrades_test.dart new file mode 100644 index 00000000..6a25d23f --- /dev/null +++ b/test/global/activate/reactivating_git_upgrades_test.dart @@ -0,0 +1,34 @@ +// 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 '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('ignores previously activated git commit', + () { + ensureGit(); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0") + ]).create(); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"], + output: ''' +Resolving dependencies... +Activated foo 1.0.0 from Git repository "../foo.git".'''); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.1") + ]).commit(); + + // Activating it again pulls down the latest commit. + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"], + output: ''' +Package foo is currently active from Git repository "../foo.git". +Resolving dependencies... +Activated foo 1.0.1 from Git repository "../foo.git".'''); + }); +} diff --git a/test/global/activate/unexpected_arguments_test.dart b/test/global/activate/unexpected_arguments_test.dart index 82a94de0..5d519177 100644 --- a/test/global/activate/unexpected_arguments_test.dart +++ b/test/global/activate/unexpected_arguments_test.dart @@ -15,7 +15,7 @@ main() { Usage: pub global activate <package...> -h, --help Print usage information for this command. -s, --source The source used to find the package. - [hosted (default), path] + [git, hosted (default), path] Run "pub help" to see global options.""", exitCode: exit_codes.USAGE); diff --git a/test/global/deactivate/git_package_test.dart b/test/global/deactivate/git_package_test.dart new file mode 100644 index 00000000..cc191913 --- /dev/null +++ b/test/global/deactivate/git_package_test.dart @@ -0,0 +1,25 @@ +// 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 '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('deactivates an active Git package', () { + ensureGit(); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0"), + d.dir("bin", [ + d.file("foo.dart", "main() => print('ok');") + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"]); + + schedulePub(args: ["global", "deactivate", "foo"], + output: 'Deactivated package foo from Git repository "../foo.git".'); + }); +} diff --git a/test/global/run/runs_git_script_test.dart b/test/global/run/runs_git_script_test.dart new file mode 100644 index 00000000..f9c1c5c5 --- /dev/null +++ b/test/global/run/runs_git_script_test.dart @@ -0,0 +1,26 @@ +// 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 '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('runs a script in a git package', () { + ensureGit(); + + d.git('foo.git', [ + d.libPubspec("foo", "1.0.0"), + d.dir("bin", [ + d.file("foo.dart", "main() => print('ok');") + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "-sgit", "../foo.git"]); + + var pub = pubRun(global: true, args: ["foo"]); + pub.stdout.expect("ok"); + pub.shouldExit(); + }); +} -- GitLab