| 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 = _getEffectiveRef(id); | 57 var ref = _getRef(id); |
| 58 if (ref == 'HEAD') return new Future.immediate(null); | 58 if (ref == null) 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, [bool fromLockFile = false]) { | 73 void validateDescription(description) { |
| 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'); | |
| 84 | 83 |
| 85 if (!description.isEmpty()) { | 84 if (!description.isEmpty()) { |
| 86 var plural = description.length > 1; | 85 var plural = description.length > 1; |
| 87 var keys = Strings.join(description.getKeys(), ', '); | 86 var keys = Strings.join(description.keys, ', '); |
| 88 throw new FormatException("Invalid key${plural ? 's' : ''}: $keys."); | 87 throw new FormatException("Invalid key${plural ? 's' : ''}: $keys."); |
| 89 } | 88 } |
| 90 } | 89 } |
| 91 | 90 |
| 92 /** | 91 /** |
| 93 * Two Git descriptions are equal if both their URLs and their refs are equal. | 92 * Two Git descriptions are equal if both their URLs and their refs are equal. |
| 94 */ | 93 */ |
| 95 bool descriptionsEqual(description1, description2) { | 94 bool descriptionsEqual(description1, description2) { |
| 96 // TODO(nweiz): Do we really want to throw an error if you have two | 95 // TODO(nweiz): Do we really want to throw an error if you have two |
| 97 // dependencies on some repo, one of which specifies a ref and one of which | 96 // dependencies on some repo, one of which specifies a ref and one of which |
| 98 // doesn't? If not, how do we handle that case in the version solver? | 97 // doesn't? If not, how do we handle that case in the version solver? |
| 99 return _getUrl(description1) == _getUrl(description2) && | 98 return _getUrl(description1) == _getUrl(description2) && |
| 100 _getRef(description1) == _getRef(description2); | 99 _getRef(description1) == _getRef(description2); |
| 101 } | 100 } |
| 102 | 101 |
| 103 /** | 102 /** |
| 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 /** | |
| 115 * Ensure that the canonical clone of the repository referred to by [id] (the | 103 * Ensure that the canonical clone of the repository referred to by [id] (the |
| 116 * one in `<system cache>/git/cache`) exists and is up-to-date. Returns a | 104 * one in `<system cache>/git/cache`) exists and is up-to-date. Returns a |
| 117 * future that completes once this is finished and throws an exception if it | 105 * future that completes once this is finished and throws an exception if it |
| 118 * fails. | 106 * fails. |
| 119 */ | 107 */ |
| 120 Future _ensureRepoCache(PackageId id) { | 108 Future _ensureRepoCache(PackageId id) { |
| 121 var path = _repoCachePath(id); | 109 var path = _repoCachePath(id); |
| 122 return exists(path).chain((exists) { | 110 return exists(path).chain((exists) { |
| 123 if (!exists) return _clone(_getUrl(id), path); | 111 if (!exists) return _clone(_getUrl(id), path); |
| 124 | 112 |
| 125 return runProcess("git", ["pull", "--force"], workingDir: path, | 113 return runProcess("git", ["pull", "--force"], workingDir: path, |
| 126 pipeStdout: true, pipeStderr: true).transform((result) { | 114 pipeStdout: true, pipeStderr: true).transform((result) { |
| 127 if (!result.success) throw 'Git failed.'; | 115 if (!result.success) throw 'Git failed.'; |
| 128 return null; | 116 return null; |
| 129 }); | 117 }); |
| 130 }); | 118 }); |
| 131 } | 119 } |
| 132 | 120 |
| 133 /** | 121 /** |
| 134 * Returns a future that completes to the revision hash of [id]. | 122 * Returns a future that completes to the revision hash of the repository for |
| 123 * [id] at [ref], which can be any Git ref. |
| 135 */ | 124 */ |
| 136 Future<String> _revisionAt(PackageId id) { | 125 Future<String> _revisionAt(PackageId id, String ref) { |
| 137 return runProcess("git", ["rev-parse", _getEffectiveRef(id)], | 126 return runProcess("git", ["rev-parse", ref], |
| 138 workingDir: _repoCachePath(id), pipeStderr: true).transform((result) { | 127 workingDir: _repoCachePath(id), pipeStderr: true).transform((result) { |
| 139 if (!result.success) throw 'Git failed.'; | 128 if (!result.success) throw 'Git failed.'; |
| 140 return result.stdout[0]; | 129 return result.stdout[0]; |
| 141 }); | 130 }); |
| 142 } | 131 } |
| 143 | 132 |
| 144 /** | 133 /** |
| 145 * Returns the path to the revision-specific cache of [id]. | 134 * Returns the path to the revision-specific cache of [id] at [ref], which can |
| 135 * be any Git ref. |
| 146 */ | 136 */ |
| 147 Future<String> _revisionCachePath(PackageId id) { | 137 Future<String> _revisionCachePath(PackageId id) { |
| 148 return _revisionAt(id).transform((rev) { | 138 var ref = _getRef(id); |
| 139 if (ref == null) ref = 'HEAD'; |
| 140 return _revisionAt(id, ref).transform((rev) { |
| 149 var revisionCacheName = '${id.name}-$rev'; | 141 var revisionCacheName = '${id.name}-$rev'; |
| 150 return join(systemCacheRoot, revisionCacheName); | 142 return join(systemCacheRoot, revisionCacheName); |
| 151 }); | 143 }); |
| 152 } | 144 } |
| 153 | 145 |
| 154 /** | 146 /** |
| 155 * Clones the repo at the URI [from] to the path [to] on the local filesystem. | 147 * Clones the repo at the URI [from] to the path [to] on the local filesystem. |
| 156 */ | 148 */ |
| 157 Future _clone(String from, String to) { | 149 Future _clone(String from, String to) { |
| 158 return runProcess("git", ["clone", from, to], pipeStdout: true, | 150 return runProcess("git", ["clone", from, to], pipeStdout: true, |
| (...skipping 28 matching lines...) Expand all Loading... |
| 187 * | 179 * |
| 188 * [description] may be a description or a [PackageId]. | 180 * [description] may be a description or a [PackageId]. |
| 189 */ | 181 */ |
| 190 String _getUrl(description) { | 182 String _getUrl(description) { |
| 191 description = _getDescription(description); | 183 description = _getDescription(description); |
| 192 if (description is String) return description; | 184 if (description is String) return description; |
| 193 return description['url']; | 185 return description['url']; |
| 194 } | 186 } |
| 195 | 187 |
| 196 /** | 188 /** |
| 197 * Returns the commit ref that should be checked out for [description]. | 189 * Returns the commit ref for [id], or null if none is given. |
| 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]. | |
| 202 * | 190 * |
| 203 * [description] may be a description or a [PackageId]. | 191 * [description] may be a description or a [PackageId]. |
| 204 */ | 192 */ |
| 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 */ | |
| 220 String _getRef(description) { | 193 String _getRef(description) { |
| 221 description = _getDescription(description); | 194 description = _getDescription(description); |
| 222 if (description is String) return null; | 195 if (description is String) return null; |
| 223 return description['ref']; | 196 return description['ref']; |
| 224 } | 197 } |
| 225 | 198 |
| 226 /** | 199 /** |
| 227 * Returns [description] if it's a description, or [PackageId.description] if | 200 * Returns [description] if it's a description, or [PackageId.description] if |
| 228 * it's a [PackageId]. | 201 * it's a [PackageId]. |
| 229 */ | 202 */ |
| 230 _getDescription(description) { | 203 _getDescription(description) { |
| 231 if (description is PackageId) return description.description; | 204 if (description is PackageId) return description.description; |
| 232 return description; | 205 return description; |
| 233 } | 206 } |
| 234 } | 207 } |
| OLD | NEW |