| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library git_source; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'package:pathos/path.dart' as path; | |
| 10 | |
| 11 import 'git.dart' as git; | |
| 12 import 'io.dart'; | |
| 13 import 'log.dart' as log; | |
| 14 import 'package.dart'; | |
| 15 import 'source.dart'; | |
| 16 import 'source_registry.dart'; | |
| 17 import 'utils.dart'; | |
| 18 | |
| 19 /// A package source that installs packages from Git repos. | |
| 20 class GitSource extends Source { | |
| 21 final String name = "git"; | |
| 22 | |
| 23 final bool shouldCache = true; | |
| 24 | |
| 25 GitSource(); | |
| 26 | |
| 27 /// Clones a Git repo to the local filesystem. | |
| 28 /// | |
| 29 /// The Git cache directory is a little idiosyncratic. At the top level, it | |
| 30 /// contains a directory for each commit of each repository, named `<package | |
| 31 /// name>-<commit hash>`. These are the canonical package directories that are | |
| 32 /// linked to from the `packages/` directory. | |
| 33 /// | |
| 34 /// In addition, the Git system cache contains a subdirectory named `cache/` | |
| 35 /// which contains a directory for each separate repository URL, named | |
| 36 /// `<package name>-<url hash>`. These are used to check out the repository | |
| 37 /// itself; each of the commit-specific directories are clones of a directory | |
| 38 /// in `cache/`. | |
| 39 Future<Package> installToSystemCache(PackageId id) { | |
| 40 var revisionCachePath; | |
| 41 | |
| 42 return git.isInstalled.then((installed) { | |
| 43 if (!installed) { | |
| 44 throw new Exception( | |
| 45 "Cannot install '${id.name}' from Git (${_getUrl(id)}).\n" | |
| 46 "Please ensure Git is correctly installed."); | |
| 47 } | |
| 48 | |
| 49 ensureDir(path.join(systemCacheRoot, 'cache')); | |
| 50 return _ensureRepoCache(id); | |
| 51 }).then((_) => systemCacheDirectory(id)).then((path) { | |
| 52 revisionCachePath = path; | |
| 53 if (entryExists(revisionCachePath)) return; | |
| 54 return _clone(_repoCachePath(id), revisionCachePath, mirror: false); | |
| 55 }).then((_) { | |
| 56 var ref = _getEffectiveRef(id); | |
| 57 if (ref == 'HEAD') return; | |
| 58 return _checkOut(revisionCachePath, ref); | |
| 59 }).then((_) { | |
| 60 return new Package.load(id.name, revisionCachePath, systemCache.sources); | |
| 61 }); | |
| 62 } | |
| 63 | |
| 64 /// Returns the path to the revision-specific cache of [id]. | |
| 65 Future<String> systemCacheDirectory(PackageId id) { | |
| 66 return _revisionAt(id).then((rev) { | |
| 67 var revisionCacheName = '${id.name}-$rev'; | |
| 68 return path.join(systemCacheRoot, revisionCacheName); | |
| 69 }); | |
| 70 } | |
| 71 | |
| 72 /// Ensures [description] is a Git URL. | |
| 73 dynamic parseDescription(String containingPath, description, | |
| 74 {bool fromLockFile: false}) { | |
| 75 // TODO(rnystrom): Handle git URLs that are relative file paths (#8570). | |
| 76 // TODO(rnystrom): Now that this function can modify the description, it | |
| 77 // may as well canonicalize it to a map so that other code in the source | |
| 78 // can assume that. | |
| 79 // A single string is assumed to be a Git URL. | |
| 80 if (description is String) return description; | |
| 81 if (description is! Map || !description.containsKey('url')) { | |
| 82 throw new FormatException("The description must be a Git URL or a map " | |
| 83 "with a 'url' key."); | |
| 84 } | |
| 85 | |
| 86 var parsed = new Map.from(description); | |
| 87 parsed.remove('url'); | |
| 88 parsed.remove('ref'); | |
| 89 if (fromLockFile) parsed.remove('resolved-ref'); | |
| 90 | |
| 91 if (!parsed.isEmpty) { | |
| 92 var plural = parsed.length > 1; | |
| 93 var keys = parsed.keys.join(', '); | |
| 94 throw new FormatException("Invalid key${plural ? 's' : ''}: $keys."); | |
| 95 } | |
| 96 | |
| 97 return description; | |
| 98 } | |
| 99 | |
| 100 /// Two Git descriptions are equal if both their URLs and their refs are | |
| 101 /// equal. | |
| 102 bool descriptionsEqual(description1, description2) { | |
| 103 // TODO(nweiz): Do we really want to throw an error if you have two | |
| 104 // dependencies on some repo, one of which specifies a ref and one of which | |
| 105 // doesn't? If not, how do we handle that case in the version solver? | |
| 106 return _getUrl(description1) == _getUrl(description2) && | |
| 107 _getRef(description1) == _getRef(description2); | |
| 108 } | |
| 109 | |
| 110 /// Attaches a specific commit to [id] to disambiguate it. | |
| 111 Future<PackageId> resolveId(PackageId id) { | |
| 112 return _revisionAt(id).then((revision) { | |
| 113 var description = {'url': _getUrl(id), 'ref': _getRef(id)}; | |
| 114 description['resolved-ref'] = revision; | |
| 115 return new PackageId(id.name, this, id.version, description); | |
| 116 }); | |
| 117 } | |
| 118 | |
| 119 // TODO(keertip): Implement getCachedPackages(). | |
| 120 | |
| 121 /// Ensure that the canonical clone of the repository referred to by [id] (the | |
| 122 /// one in `<system cache>/git/cache`) exists and is up-to-date. Returns a | |
| 123 /// future that completes once this is finished and throws an exception if it | |
| 124 /// fails. | |
| 125 Future _ensureRepoCache(PackageId id) { | |
| 126 return new Future.sync(() { | |
| 127 var path = _repoCachePath(id); | |
| 128 if (!entryExists(path)) return _clone(_getUrl(id), path, mirror: true); | |
| 129 return git.run(["fetch"], workingDir: path).then((result) => null); | |
| 130 }); | |
| 131 } | |
| 132 | |
| 133 /// Returns a future that completes to the revision hash of [id]. | |
| 134 Future<String> _revisionAt(PackageId id) { | |
| 135 return git.run(["rev-parse", _getEffectiveRef(id)], | |
| 136 workingDir: _repoCachePath(id)).then((result) => result[0]); | |
| 137 } | |
| 138 | |
| 139 /// Clones the repo at the URI [from] to the path [to] on the local | |
| 140 /// filesystem. | |
| 141 /// | |
| 142 /// If [mirror] is true, create a bare, mirrored clone. This doesn't check out | |
| 143 /// the working tree, but instead makes the repository a local mirror of the | |
| 144 /// remote repository. See the manpage for `git clone` for more information. | |
| 145 Future _clone(String from, String to, {bool mirror: false}) { | |
| 146 return new Future.sync(() { | |
| 147 // Git on Windows does not seem to automatically create the destination | |
| 148 // directory. | |
| 149 ensureDir(to); | |
| 150 var args = ["clone", from, to]; | |
| 151 if (mirror) args.insert(1, "--mirror"); | |
| 152 return git.run(args); | |
| 153 }).then((result) => null); | |
| 154 } | |
| 155 | |
| 156 /// Checks out the reference [ref] in [repoPath]. | |
| 157 Future _checkOut(String repoPath, String ref) { | |
| 158 return git.run(["checkout", ref], workingDir: repoPath).then( | |
| 159 (result) => null); | |
| 160 } | |
| 161 | |
| 162 /// Returns the path to the canonical clone of the repository referred to by | |
| 163 /// [id] (the one in `<system cache>/git/cache`). | |
| 164 String _repoCachePath(PackageId id) { | |
| 165 var repoCacheName = '${id.name}-${sha1(_getUrl(id))}'; | |
| 166 return path.join(systemCacheRoot, 'cache', repoCacheName); | |
| 167 } | |
| 168 | |
| 169 /// Returns the repository URL for [id]. | |
| 170 /// | |
| 171 /// [description] may be a description or a [PackageId]. | |
| 172 String _getUrl(description) { | |
| 173 description = _getDescription(description); | |
| 174 if (description is String) return description; | |
| 175 return description['url']; | |
| 176 } | |
| 177 | |
| 178 /// Returns the commit ref that should be checked out for [description]. | |
| 179 /// | |
| 180 /// This differs from [_getRef] in that it doesn't just return the ref in | |
| 181 /// [description]. It will return a sensible default if that ref doesn't | |
| 182 /// exist, and it will respect the "resolved-ref" parameter set by | |
| 183 /// [resolveId]. | |
| 184 /// | |
| 185 /// [description] may be a description or a [PackageId]. | |
| 186 String _getEffectiveRef(description) { | |
| 187 description = _getDescription(description); | |
| 188 if (description is Map && description.containsKey('resolved-ref')) { | |
| 189 return description['resolved-ref']; | |
| 190 } | |
| 191 | |
| 192 var ref = _getRef(description); | |
| 193 return ref == null ? 'HEAD' : ref; | |
| 194 } | |
| 195 | |
| 196 /// Returns the commit ref for [description], or null if none is given. | |
| 197 /// | |
| 198 /// [description] may be a description or a [PackageId]. | |
| 199 String _getRef(description) { | |
| 200 description = _getDescription(description); | |
| 201 if (description is String) return null; | |
| 202 return description['ref']; | |
| 203 } | |
| 204 | |
| 205 /// Returns [description] if it's a description, or [PackageId.description] if | |
| 206 /// it's a [PackageId]. | |
| 207 _getDescription(description) { | |
| 208 if (description is PackageId) return description.description; | |
| 209 return description; | |
| 210 } | |
| 211 } | |
| OLD | NEW |