OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
6 | 6 |
7 import 'package:path/path.dart' as path; | 7 import 'package:path/path.dart' as path; |
8 import 'package:pub_semver/pub_semver.dart'; | 8 import 'package:pub_semver/pub_semver.dart'; |
9 | 9 |
10 import '../git.dart' as git; | 10 import '../git.dart' as git; |
11 import '../io.dart'; | 11 import '../io.dart'; |
12 import '../log.dart' as log; | 12 import '../log.dart' as log; |
13 import '../package.dart'; | 13 import '../package.dart'; |
14 import '../pubspec.dart'; | 14 import '../pubspec.dart'; |
| 15 import '../source.dart'; |
| 16 import '../system_cache.dart'; |
15 import '../utils.dart'; | 17 import '../utils.dart'; |
16 import 'cached.dart'; | 18 import 'cached.dart'; |
17 | 19 |
18 /// A package source that gets packages from Git repos. | 20 /// A package source that gets packages from Git repos. |
19 class GitSource extends CachedSource { | 21 class GitSource extends Source { |
| 22 final name = "git"; |
| 23 |
| 24 BoundGitSource bind(SystemCache systemCache) => |
| 25 new BoundGitSource(this, systemCache); |
| 26 |
20 /// Returns a reference to a git package with the given [name] and [url]. | 27 /// Returns a reference to a git package with the given [name] and [url]. |
21 /// | 28 /// |
22 /// If passed, [reference] is the Git reference. It defaults to `"HEAD"`. | 29 /// If passed, [reference] is the Git reference. It defaults to `"HEAD"`. |
23 static PackageRef refFor(String name, String url, {String reference}) => | 30 PackageRef refFor(String name, String url, {String reference}) => |
24 new PackageRef(name, "git", {'url': url, 'ref': reference ?? 'HEAD'}); | 31 new PackageRef(name, "git", {'url': url, 'ref': reference ?? 'HEAD'}); |
25 | 32 |
26 /// Given a valid git package description, returns the URL of the repository | 33 /// Given a valid git package description, returns the URL of the repository |
27 /// it pulls from. | 34 /// it pulls from. |
28 static String urlFromDescription(description) => description["url"]; | 35 String urlFromDescription(description) => description["url"]; |
29 | |
30 final name = "git"; | |
31 | |
32 /// The paths to the canonical clones of repositories for which "git fetch" | |
33 /// has already been run during this run of pub. | |
34 final _updatedRepos = new Set<String>(); | |
35 | |
36 /// Given a Git repo that contains a pub package, gets the name of the pub | |
37 /// package. | |
38 Future<String> getPackageNameFromRepo(String repo) { | |
39 // Clone the repo to a temp directory. | |
40 return withTempDir((tempDir) { | |
41 return _clone(repo, tempDir, shallow: true).then((_) { | |
42 var pubspec = new Pubspec.load(tempDir, systemCache.sources); | |
43 return pubspec.name; | |
44 }); | |
45 }); | |
46 } | |
47 | |
48 Future<List<PackageId>> doGetVersions(PackageRef ref) async { | |
49 await _ensureRepoCache(ref); | |
50 var path = _repoCachePath(ref); | |
51 var revision = await _firstRevision(path, ref.description['ref']); | |
52 var pubspec = await _describeUncached(ref, revision); | |
53 | |
54 return [ | |
55 new PackageId(ref.name, name, pubspec.version, { | |
56 'url': ref.description['url'], | |
57 'ref': ref.description['ref'], | |
58 'resolved-ref': revision | |
59 }) | |
60 ]; | |
61 } | |
62 | |
63 /// Since we don't have an easy way to read from a remote Git repo, this | |
64 /// just installs [id] into the system cache, then describes it from there. | |
65 Future<Pubspec> describeUncached(PackageId id) => | |
66 _describeUncached(id.toRef(), id.description['resolved-ref']); | |
67 | |
68 /// Like [describeUncached], but takes a separate [ref] and Git [revision] | |
69 /// rather than a single ID. | |
70 Future<Pubspec> _describeUncached(PackageRef ref, String revision) async { | |
71 await _ensureRevision(ref, revision); | |
72 var path = _repoCachePath(ref); | |
73 | |
74 var lines; | |
75 try { | |
76 lines = await git.run(["show", "$revision:pubspec.yaml"], | |
77 workingDir: path); | |
78 } on git.GitException catch (_) { | |
79 fail('Could not find a file named "pubspec.yaml" in ' | |
80 '${ref.description['url']} $revision.'); | |
81 } | |
82 | |
83 return new Pubspec.parse(lines.join("\n"), systemCache.sources, | |
84 expectedName: ref.name); | |
85 } | |
86 | |
87 /// Clones a Git repo to the local filesystem. | |
88 /// | |
89 /// The Git cache directory is a little idiosyncratic. At the top level, it | |
90 /// contains a directory for each commit of each repository, named `<package | |
91 /// name>-<commit hash>`. These are the canonical package directories that are | |
92 /// linked to from the `packages/` directory. | |
93 /// | |
94 /// In addition, the Git system cache contains a subdirectory named `cache/` | |
95 /// which contains a directory for each separate repository URL, named | |
96 /// `<package name>-<url hash>`. These are used to check out the repository | |
97 /// itself; each of the commit-specific directories are clones of a directory | |
98 /// in `cache/`. | |
99 Future<Package> downloadToSystemCache(PackageId id) async { | |
100 var ref = id.toRef(); | |
101 if (!git.isInstalled) { | |
102 fail("Cannot get ${id.name} from Git (${ref.description['url']}).\n" | |
103 "Please ensure Git is correctly installed."); | |
104 } | |
105 | |
106 ensureDir(path.join(systemCacheRoot, 'cache')); | |
107 await _ensureRevision(ref, id.description['resolved-ref']); | |
108 | |
109 var revisionCachePath = getDirectory(id); | |
110 if (!entryExists(revisionCachePath)) { | |
111 await _clone(_repoCachePath(ref), revisionCachePath); | |
112 await _checkOut(revisionCachePath, id.description['resolved-ref']); | |
113 } | |
114 | |
115 return new Package.load(id.name, revisionCachePath, systemCache.sources); | |
116 } | |
117 | |
118 /// Returns the path to the revision-specific cache of [id]. | |
119 String getDirectory(PackageId id) => path.join( | |
120 systemCacheRoot, "${id.name}-${id.description['resolved-ref']}"); | |
121 | 36 |
122 PackageRef parseRef(String name, description, {String containingPath}) { | 37 PackageRef parseRef(String name, description, {String containingPath}) { |
123 // TODO(rnystrom): Handle git URLs that are relative file paths (#8570). | 38 // TODO(rnystrom): Handle git URLs that are relative file paths (#8570). |
124 if (description is String) description = {'url': description}; | 39 if (description is String) description = {'url': description}; |
125 | 40 |
126 if (description is! Map) { | 41 if (description is! Map) { |
127 throw new FormatException("The description must be a Git URL or a map " | 42 throw new FormatException("The description must be a Git URL or a map " |
128 "with a 'url' key."); | 43 "with a 'url' key."); |
129 } | 44 } |
130 | 45 |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
210 if (description1['url'] != description2['url']) return false; | 125 if (description1['url'] != description2['url']) return false; |
211 if (description1['ref'] != description2['ref']) return false; | 126 if (description1['ref'] != description2['ref']) return false; |
212 | 127 |
213 if (description1.containsKey('resolved-ref') && | 128 if (description1.containsKey('resolved-ref') && |
214 description2.containsKey('resolved-ref')) { | 129 description2.containsKey('resolved-ref')) { |
215 return description1['resolved-ref'] == description2['resolved-ref']; | 130 return description1['resolved-ref'] == description2['resolved-ref']; |
216 } | 131 } |
217 | 132 |
218 return true; | 133 return true; |
219 } | 134 } |
| 135 } |
| 136 |
| 137 /// The [BoundSource] for [GitSource]. |
| 138 class BoundGitSource extends CachedSource { |
| 139 final GitSource source; |
| 140 |
| 141 final SystemCache systemCache; |
| 142 |
| 143 BoundGitSource(this.source, this.systemCache); |
| 144 |
| 145 /// The paths to the canonical clones of repositories for which "git fetch" |
| 146 /// has already been run during this run of pub. |
| 147 final _updatedRepos = new Set<String>(); |
| 148 |
| 149 /// Given a Git repo that contains a pub package, gets the name of the pub |
| 150 /// package. |
| 151 Future<String> getPackageNameFromRepo(String repo) { |
| 152 // Clone the repo to a temp directory. |
| 153 return withTempDir((tempDir) { |
| 154 return _clone(repo, tempDir, shallow: true).then((_) { |
| 155 var pubspec = new Pubspec.load(tempDir, systemCache.sources); |
| 156 return pubspec.name; |
| 157 }); |
| 158 }); |
| 159 } |
| 160 |
| 161 Future<List<PackageId>> doGetVersions(PackageRef ref) async { |
| 162 await _ensureRepoCache(ref); |
| 163 var path = _repoCachePath(ref); |
| 164 var revision = await _firstRevision(path, ref.description['ref']); |
| 165 var pubspec = await _describeUncached(ref, revision); |
| 166 |
| 167 return [ |
| 168 new PackageId(ref.name, source.name, pubspec.version, { |
| 169 'url': ref.description['url'], |
| 170 'ref': ref.description['ref'], |
| 171 'resolved-ref': revision |
| 172 }) |
| 173 ]; |
| 174 } |
| 175 |
| 176 /// Since we don't have an easy way to read from a remote Git repo, this |
| 177 /// just installs [id] into the system cache, then describes it from there. |
| 178 Future<Pubspec> describeUncached(PackageId id) => |
| 179 _describeUncached(id.toRef(), id.description['resolved-ref']); |
| 180 |
| 181 /// Like [describeUncached], but takes a separate [ref] and Git [revision] |
| 182 /// rather than a single ID. |
| 183 Future<Pubspec> _describeUncached(PackageRef ref, String revision) async { |
| 184 await _ensureRevision(ref, revision); |
| 185 var path = _repoCachePath(ref); |
| 186 |
| 187 var lines; |
| 188 try { |
| 189 lines = await git.run(["show", "$revision:pubspec.yaml"], |
| 190 workingDir: path); |
| 191 } on git.GitException catch (_) { |
| 192 fail('Could not find a file named "pubspec.yaml" in ' |
| 193 '${ref.description['url']} $revision.'); |
| 194 } |
| 195 |
| 196 return new Pubspec.parse(lines.join("\n"), systemCache.sources, |
| 197 expectedName: ref.name); |
| 198 } |
| 199 |
| 200 /// Clones a Git repo to the local filesystem. |
| 201 /// |
| 202 /// The Git cache directory is a little idiosyncratic. At the top level, it |
| 203 /// contains a directory for each commit of each repository, named `<package |
| 204 /// name>-<commit hash>`. These are the canonical package directories that are |
| 205 /// linked to from the `packages/` directory. |
| 206 /// |
| 207 /// In addition, the Git system cache contains a subdirectory named `cache/` |
| 208 /// which contains a directory for each separate repository URL, named |
| 209 /// `<package name>-<url hash>`. These are used to check out the repository |
| 210 /// itself; each of the commit-specific directories are clones of a directory |
| 211 /// in `cache/`. |
| 212 Future<Package> downloadToSystemCache(PackageId id) async { |
| 213 var ref = id.toRef(); |
| 214 if (!git.isInstalled) { |
| 215 fail("Cannot get ${id.name} from Git (${ref.description['url']}).\n" |
| 216 "Please ensure Git is correctly installed."); |
| 217 } |
| 218 |
| 219 ensureDir(path.join(systemCacheRoot, 'cache')); |
| 220 await _ensureRevision(ref, id.description['resolved-ref']); |
| 221 |
| 222 var revisionCachePath = getDirectory(id); |
| 223 if (!entryExists(revisionCachePath)) { |
| 224 await _clone(_repoCachePath(ref), revisionCachePath); |
| 225 await _checkOut(revisionCachePath, id.description['resolved-ref']); |
| 226 } |
| 227 |
| 228 return new Package.load(id.name, revisionCachePath, systemCache.sources); |
| 229 } |
| 230 |
| 231 /// Returns the path to the revision-specific cache of [id]. |
| 232 String getDirectory(PackageId id) => path.join( |
| 233 systemCacheRoot, "${id.name}-${id.description['resolved-ref']}"); |
220 | 234 |
221 List<Package> getCachedPackages() { | 235 List<Package> getCachedPackages() { |
222 // TODO(keertip): Implement getCachedPackages(). | 236 // TODO(keertip): Implement getCachedPackages(). |
223 throw new UnimplementedError( | 237 throw new UnimplementedError( |
224 "The git source doesn't support listing its cached packages yet."); | 238 "The git source doesn't support listing its cached packages yet."); |
225 } | 239 } |
226 | 240 |
227 /// Resets all cached packages back to the pristine state of the Git | 241 /// Resets all cached packages back to the pristine state of the Git |
228 /// repository at the revision they are pinned to. | 242 /// repository at the revision they are pinned to. |
229 Future<Pair<List<PackageId>, List<PackageId>>> repairCachedPackages() async { | 243 Future<Pair<List<PackageId>, List<PackageId>>> repairCachedPackages() async { |
230 if (!dirExists(systemCacheRoot)) return new Pair([], []); | 244 if (!dirExists(systemCacheRoot)) return new Pair([], []); |
231 | 245 |
232 var successes = []; | 246 var successes = []; |
233 var failures = []; | 247 var failures = []; |
234 | 248 |
235 var packages = listDir(systemCacheRoot) | 249 var packages = listDir(systemCacheRoot) |
236 .where((entry) => dirExists(path.join(entry, ".git"))) | 250 .where((entry) => dirExists(path.join(entry, ".git"))) |
237 .map((packageDir) => new Package.load(null, packageDir, | 251 .map((packageDir) => new Package.load( |
238 systemCache.sources)) | 252 null, packageDir, systemCache.sources)) |
239 .toList(); | 253 .toList(); |
240 | 254 |
241 // Note that there may be multiple packages with the same name and version | 255 // Note that there may be multiple packages with the same name and version |
242 // (pinned to different commits). The sort order of those is unspecified. | 256 // (pinned to different commits). The sort order of those is unspecified. |
243 packages.sort(Package.orderByNameAndVersion); | 257 packages.sort(Package.orderByNameAndVersion); |
244 | 258 |
245 for (var package in packages) { | 259 for (var package in packages) { |
246 var id = new PackageId(package.name, this.name, package.version, null); | 260 var id = new PackageId(package.name, source.name, package.version, null); |
247 | 261 |
248 log.message("Resetting Git repository for " | 262 log.message("Resetting Git repository for " |
249 "${log.bold(package.name)} ${package.version}..."); | 263 "${log.bold(package.name)} ${package.version}..."); |
250 | 264 |
251 try { | 265 try { |
252 // Remove all untracked files. | 266 // Remove all untracked files. |
253 await git.run(["clean", "-d", "--force", "-x"], | 267 await git.run(["clean", "-d", "--force", "-x"], |
254 workingDir: package.dir); | 268 workingDir: package.dir); |
255 | 269 |
256 // Discard all changes to tracked files. | 270 // Discard all changes to tracked files. |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
362 (result) => null); | 376 (result) => null); |
363 } | 377 } |
364 | 378 |
365 /// Returns the path to the canonical clone of the repository referred to by | 379 /// Returns the path to the canonical clone of the repository referred to by |
366 /// [id] (the one in `<system cache>/git/cache`). | 380 /// [id] (the one in `<system cache>/git/cache`). |
367 String _repoCachePath(PackageRef ref) { | 381 String _repoCachePath(PackageRef ref) { |
368 var repoCacheName = '${ref.name}-${sha1(ref.description['url'])}'; | 382 var repoCacheName = '${ref.name}-${sha1(ref.description['url'])}'; |
369 return path.join(systemCacheRoot, 'cache', repoCacheName); | 383 return path.join(systemCacheRoot, 'cache', repoCacheName); |
370 } | 384 } |
371 } | 385 } |
OLD | NEW |