| 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 #library('git_source'); | 5 #library('git_source'); |
| 6 | 6 |
| 7 #import('io.dart'); | 7 #import('io.dart'); |
| 8 #import('package.dart'); | 8 #import('package.dart'); |
| 9 #import('source.dart'); | 9 #import('source.dart'); |
| 10 #import('source_registry.dart'); | 10 #import('source_registry.dart'); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 47 return ensureDir(join(systemCacheRoot, 'cache')); | 47 return ensureDir(join(systemCacheRoot, 'cache')); |
| 48 }).chain((_) => _ensureRepoCache(id)) | 48 }).chain((_) => _ensureRepoCache(id)) |
| 49 .chain((_) => _revisionCachePath(id)) | 49 .chain((_) => _revisionCachePath(id)) |
| 50 .chain((path) { | 50 .chain((path) { |
| 51 revisionCachePath = path; | 51 revisionCachePath = path; |
| 52 return exists(revisionCachePath); | 52 return exists(revisionCachePath); |
| 53 }).chain((exists) { | 53 }).chain((exists) { |
| 54 if (exists) return new Future.immediate(null); | 54 if (exists) return new Future.immediate(null); |
| 55 return _clone(_repoCachePath(id), revisionCachePath); | 55 return _clone(_repoCachePath(id), revisionCachePath); |
| 56 }).chain((_) { | 56 }).chain((_) { |
| 57 var ref = _getRef(id); | 57 var ref = _getEffectiveRef(id); |
| 58 if (ref == null) return new Future.immediate(null); | 58 if (ref == 'HEAD') return new Future.immediate(null); |
| 59 return _checkOut(revisionCachePath, ref); | 59 return _checkOut(revisionCachePath, ref); |
| 60 }).chain((_) => Package.load(revisionCachePath, systemCache.sources)); | 60 }).chain((_) => Package.load(revisionCachePath, systemCache.sources)); |
| 61 } | 61 } |
| 62 | 62 |
| 63 /** | 63 /** |
| 64 * The package name of a Git repo is the name of the directory into which | 64 * The package name of a Git repo is the name of the directory into which |
| 65 * it'll be cloned. | 65 * it'll be cloned. |
| 66 */ | 66 */ |
| 67 String packageName(description) => | 67 String packageName(description) => |
| 68 basename(_getUrl(description)).replaceFirst(const RegExp("\.git\$"), ""); | 68 basename(_getUrl(description)).replaceFirst(const RegExp("\.git\$"), ""); |
| 69 | 69 |
| 70 /** | 70 /** |
| 71 * Ensures [description] is a Git URL. | 71 * Ensures [description] is a Git URL. |
| 72 */ | 72 */ |
| 73 void validateDescription(description) { | 73 void validateDescription(description, [bool fromLockFile = false]) { |
| 74 // A single string is assumed to be a Git URL. | 74 // A single string is assumed to be a Git URL. |
| 75 if (description is String) return; | 75 if (description is String) return; |
| 76 if (description is! Map || !description.containsKey('url')) { | 76 if (description is! Map || !description.containsKey('url')) { |
| 77 throw new FormatException("The description must be a Git URL or a map " | 77 throw new FormatException("The description must be a Git URL or a map " |
| 78 "with a 'url' key."); | 78 "with a 'url' key."); |
| 79 } | 79 } |
| 80 description = new Map.from(description); | 80 description = new Map.from(description); |
| 81 description.remove('url'); | 81 description.remove('url'); |
| 82 description.remove('ref'); | 82 description.remove('ref'); |
| 83 if (fromLockFile) description.remove('resolved-ref'); |
| 83 | 84 |
| 84 if (!description.isEmpty()) { | 85 if (!description.isEmpty()) { |
| 85 var plural = description.length > 1; | 86 var plural = description.length > 1; |
| 86 var keys = Strings.join(description.keys, ', '); | 87 var keys = Strings.join(description.getKeys(), ', '); |
| 87 throw new FormatException("Invalid key${plural ? 's' : ''}: $keys."); | 88 throw new FormatException("Invalid key${plural ? 's' : ''}: $keys."); |
| 88 } | 89 } |
| 89 } | 90 } |
| 90 | 91 |
| 91 /** | 92 /** |
| 92 * Two Git descriptions are equal if both their URLs and their refs are equal. | 93 * Two Git descriptions are equal if both their URLs and their refs are equal. |
| 93 */ | 94 */ |
| 94 bool descriptionsEqual(description1, description2) { | 95 bool descriptionsEqual(description1, description2) { |
| 95 // TODO(nweiz): Do we really want to throw an error if you have two | 96 // TODO(nweiz): Do we really want to throw an error if you have two |
| 96 // dependencies on some repo, one of which specifies a ref and one of which | 97 // dependencies on some repo, one of which specifies a ref and one of which |
| 97 // doesn't? If not, how do we handle that case in the version solver? | 98 // doesn't? If not, how do we handle that case in the version solver? |
| 98 return _getUrl(description1) == _getUrl(description2) && | 99 return _getUrl(description1) == _getUrl(description2) && |
| 99 _getRef(description1) == _getRef(description2); | 100 _getRef(description1) == _getRef(description2); |
| 100 } | 101 } |
| 101 | 102 |
| 102 /** | 103 /** |
| 104 * Attaches a specific commit to [id] to disambiguate it. |
| 105 */ |
| 106 Future<PackageId> resolveId(PackageId id) { |
| 107 return _revisionAt(id).transform((revision) { |
| 108 var description = {'url': _getUrl(id), 'ref': _getRef(id)}; |
| 109 description['resolved-ref'] = revision; |
| 110 return new PackageId(this, id.version, description); |
| 111 }); |
| 112 } |
| 113 |
| 114 /** |
| 103 * Ensure that the canonical clone of the repository referred to by [id] (the | 115 * Ensure that the canonical clone of the repository referred to by [id] (the |
| 104 * one in `<system cache>/git/cache`) exists and is up-to-date. Returns a | 116 * one in `<system cache>/git/cache`) exists and is up-to-date. Returns a |
| 105 * future that completes once this is finished and throws an exception if it | 117 * future that completes once this is finished and throws an exception if it |
| 106 * fails. | 118 * fails. |
| 107 */ | 119 */ |
| 108 Future _ensureRepoCache(PackageId id) { | 120 Future _ensureRepoCache(PackageId id) { |
| 109 var path = _repoCachePath(id); | 121 var path = _repoCachePath(id); |
| 110 return exists(path).chain((exists) { | 122 return exists(path).chain((exists) { |
| 111 if (!exists) return _clone(_getUrl(id), path); | 123 if (!exists) return _clone(_getUrl(id), path); |
| 112 | 124 |
| 113 return runProcess("git", ["pull", "--force"], workingDir: path, | 125 return runProcess("git", ["pull", "--force"], workingDir: path, |
| 114 pipeStdout: true, pipeStderr: true).transform((result) { | 126 pipeStdout: true, pipeStderr: true).transform((result) { |
| 115 if (!result.success) throw 'Git failed.'; | 127 if (!result.success) throw 'Git failed.'; |
| 116 return null; | 128 return null; |
| 117 }); | 129 }); |
| 118 }); | 130 }); |
| 119 } | 131 } |
| 120 | 132 |
| 121 /** | 133 /** |
| 122 * Returns a future that completes to the revision hash of the repository for | 134 * Returns a future that completes to the revision hash of [id]. |
| 123 * [id] at [ref], which can be any Git ref. | |
| 124 */ | 135 */ |
| 125 Future<String> _revisionAt(PackageId id, String ref) { | 136 Future<String> _revisionAt(PackageId id) { |
| 126 return runProcess("git", ["rev-parse", ref], | 137 return runProcess("git", ["rev-parse", _getEffectiveRef(id)], |
| 127 workingDir: _repoCachePath(id), pipeStderr: true).transform((result) { | 138 workingDir: _repoCachePath(id), pipeStderr: true).transform((result) { |
| 128 if (!result.success) throw 'Git failed.'; | 139 if (!result.success) throw 'Git failed.'; |
| 129 return result.stdout[0]; | 140 return result.stdout[0]; |
| 130 }); | 141 }); |
| 131 } | 142 } |
| 132 | 143 |
| 133 /** | 144 /** |
| 134 * Returns the path to the revision-specific cache of [id] at [ref], which can | 145 * Returns the path to the revision-specific cache of [id]. |
| 135 * be any Git ref. | |
| 136 */ | 146 */ |
| 137 Future<String> _revisionCachePath(PackageId id) { | 147 Future<String> _revisionCachePath(PackageId id) { |
| 138 var ref = _getRef(id); | 148 return _revisionAt(id).transform((rev) { |
| 139 if (ref == null) ref = 'HEAD'; | |
| 140 return _revisionAt(id, ref).transform((rev) { | |
| 141 var revisionCacheName = '${id.name}-$rev'; | 149 var revisionCacheName = '${id.name}-$rev'; |
| 142 return join(systemCacheRoot, revisionCacheName); | 150 return join(systemCacheRoot, revisionCacheName); |
| 143 }); | 151 }); |
| 144 } | 152 } |
| 145 | 153 |
| 146 /** | 154 /** |
| 147 * Clones the repo at the URI [from] to the path [to] on the local filesystem. | 155 * Clones the repo at the URI [from] to the path [to] on the local filesystem. |
| 148 */ | 156 */ |
| 149 Future _clone(String from, String to) { | 157 Future _clone(String from, String to) { |
| 150 return runProcess("git", ["clone", from, to], pipeStdout: true, | 158 return runProcess("git", ["clone", from, to], pipeStdout: true, |
| (...skipping 28 matching lines...) Expand all Loading... |
| 179 * | 187 * |
| 180 * [description] may be a description or a [PackageId]. | 188 * [description] may be a description or a [PackageId]. |
| 181 */ | 189 */ |
| 182 String _getUrl(description) { | 190 String _getUrl(description) { |
| 183 description = _getDescription(description); | 191 description = _getDescription(description); |
| 184 if (description is String) return description; | 192 if (description is String) return description; |
| 185 return description['url']; | 193 return description['url']; |
| 186 } | 194 } |
| 187 | 195 |
| 188 /** | 196 /** |
| 189 * Returns the commit ref for [id], or null if none is given. | 197 * Returns the commit ref that should be checked out for [description]. |
| 198 * |
| 199 * This differs from [_getRef] in that it doesn't just return the ref in |
| 200 * [description]. It will return a sensible default if that ref doesn't exist, |
| 201 * and it will respect the "resolved-ref" parameter set by [resolveId]. |
| 190 * | 202 * |
| 191 * [description] may be a description or a [PackageId]. | 203 * [description] may be a description or a [PackageId]. |
| 192 */ | 204 */ |
| 205 String _getEffectiveRef(description) { |
| 206 description = _getDescription(description); |
| 207 if (description is Map && description.containsKey('resolved-ref')) { |
| 208 return description['resolved-ref']; |
| 209 } |
| 210 |
| 211 var ref = _getRef(description); |
| 212 return ref == null ? 'HEAD' : ref; |
| 213 } |
| 214 |
| 215 /** |
| 216 * Returns the commit ref for [description], or null if none is given. |
| 217 * |
| 218 * [description] may be a description or a [PackageId]. |
| 219 */ |
| 193 String _getRef(description) { | 220 String _getRef(description) { |
| 194 description = _getDescription(description); | 221 description = _getDescription(description); |
| 195 if (description is String) return null; | 222 if (description is String) return null; |
| 196 return description['ref']; | 223 return description['ref']; |
| 197 } | 224 } |
| 198 | 225 |
| 199 /** | 226 /** |
| 200 * Returns [description] if it's a description, or [PackageId.description] if | 227 * Returns [description] if it's a description, or [PackageId.description] if |
| 201 * it's a [PackageId]. | 228 * it's a [PackageId]. |
| 202 */ | 229 */ |
| 203 _getDescription(description) { | 230 _getDescription(description) { |
| 204 if (description is PackageId) return description.description; | 231 if (description is PackageId) return description.description; |
| 205 return description; | 232 return description; |
| 206 } | 233 } |
| 207 } | 234 } |
| OLD | NEW |