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 'dart:async'; |
7 import 'git.dart' as git; | 8 import 'git.dart' as git; |
8 import 'io.dart'; | 9 import 'io.dart'; |
9 import 'package.dart'; | 10 import 'package.dart'; |
10 import 'source.dart'; | 11 import 'source.dart'; |
11 import 'source_registry.dart'; | 12 import 'source_registry.dart'; |
12 import 'utils.dart'; | 13 import 'utils.dart'; |
13 | 14 |
14 /// A package source that installs packages from Git repos. | 15 /// A package source that installs packages from Git repos. |
15 class GitSource extends Source { | 16 class GitSource extends Source { |
16 final String name = "git"; | 17 final String name = "git"; |
(...skipping 10 matching lines...) Expand all Loading... |
27 /// linked to from the `packages/` directory. | 28 /// linked to from the `packages/` directory. |
28 /// | 29 /// |
29 /// In addition, the Git system cache contains a subdirectory named `cache/` | 30 /// In addition, the Git system cache contains a subdirectory named `cache/` |
30 /// which contains a directory for each separate repository URL, named | 31 /// which contains a directory for each separate repository URL, named |
31 /// `<package name>-<url hash>`. These are used to check out the repository | 32 /// `<package name>-<url hash>`. These are used to check out the repository |
32 /// itself; each of the commit-specific directories are clones of a directory | 33 /// itself; each of the commit-specific directories are clones of a directory |
33 /// in `cache/`. | 34 /// in `cache/`. |
34 Future<Package> installToSystemCache(PackageId id) { | 35 Future<Package> installToSystemCache(PackageId id) { |
35 var revisionCachePath; | 36 var revisionCachePath; |
36 | 37 |
37 return git.isInstalled.chain((installed) { | 38 return git.isInstalled.then((installed) { |
38 if (!installed) { | 39 if (!installed) { |
39 throw new Exception( | 40 throw new Exception( |
40 "Cannot install '${id.name}' from Git (${_getUrl(id)}).\n" | 41 "Cannot install '${id.name}' from Git (${_getUrl(id)}).\n" |
41 "Please ensure Git is correctly installed."); | 42 "Please ensure Git is correctly installed."); |
42 } | 43 } |
43 | 44 |
44 return ensureDir(join(systemCacheRoot, 'cache')); | 45 return ensureDir(join(systemCacheRoot, 'cache')); |
45 }).chain((_) => _ensureRepoCache(id)) | 46 }).then((_) => _ensureRepoCache(id)) |
46 .chain((_) => _revisionCachePath(id)) | 47 .then((_) => _revisionCachePath(id)) |
47 .chain((path) { | 48 .then((path) { |
48 revisionCachePath = path; | 49 revisionCachePath = path; |
49 return exists(revisionCachePath); | 50 return exists(revisionCachePath); |
50 }).chain((exists) { | 51 }).then((exists) { |
51 if (exists) return new Future.immediate(null); | 52 if (exists) return new Future.immediate(null); |
52 return _clone(_repoCachePath(id), revisionCachePath, mirror: false); | 53 return _clone(_repoCachePath(id), revisionCachePath, mirror: false); |
53 }).chain((_) { | 54 }).then((_) { |
54 var ref = _getEffectiveRef(id); | 55 var ref = _getEffectiveRef(id); |
55 if (ref == 'HEAD') return new Future.immediate(null); | 56 if (ref == 'HEAD') return new Future.immediate(null); |
56 return _checkOut(revisionCachePath, ref); | 57 return _checkOut(revisionCachePath, ref); |
57 }).chain((_) { | 58 }).then((_) { |
58 return Package.load(id.name, revisionCachePath, systemCache.sources); | 59 return Package.load(id.name, revisionCachePath, systemCache.sources); |
59 }); | 60 }); |
60 } | 61 } |
61 | 62 |
62 /// Ensures [description] is a Git URL. | 63 /// Ensures [description] is a Git URL. |
63 void validateDescription(description, {bool fromLockFile: false}) { | 64 void validateDescription(description, {bool fromLockFile: false}) { |
64 // A single string is assumed to be a Git URL. | 65 // A single string is assumed to be a Git URL. |
65 if (description is String) return; | 66 if (description is String) return; |
66 if (description is! Map || !description.containsKey('url')) { | 67 if (description is! Map || !description.containsKey('url')) { |
67 throw new FormatException("The description must be a Git URL or a map " | 68 throw new FormatException("The description must be a Git URL or a map " |
(...skipping 16 matching lines...) Expand all Loading... |
84 bool descriptionsEqual(description1, description2) { | 85 bool descriptionsEqual(description1, description2) { |
85 // TODO(nweiz): Do we really want to throw an error if you have two | 86 // TODO(nweiz): Do we really want to throw an error if you have two |
86 // dependencies on some repo, one of which specifies a ref and one of which | 87 // dependencies on some repo, one of which specifies a ref and one of which |
87 // doesn't? If not, how do we handle that case in the version solver? | 88 // doesn't? If not, how do we handle that case in the version solver? |
88 return _getUrl(description1) == _getUrl(description2) && | 89 return _getUrl(description1) == _getUrl(description2) && |
89 _getRef(description1) == _getRef(description2); | 90 _getRef(description1) == _getRef(description2); |
90 } | 91 } |
91 | 92 |
92 /// Attaches a specific commit to [id] to disambiguate it. | 93 /// Attaches a specific commit to [id] to disambiguate it. |
93 Future<PackageId> resolveId(PackageId id) { | 94 Future<PackageId> resolveId(PackageId id) { |
94 return _revisionAt(id).transform((revision) { | 95 return _revisionAt(id).then((revision) { |
95 var description = {'url': _getUrl(id), 'ref': _getRef(id)}; | 96 var description = {'url': _getUrl(id), 'ref': _getRef(id)}; |
96 description['resolved-ref'] = revision; | 97 description['resolved-ref'] = revision; |
97 return new PackageId(id.name, this, id.version, description); | 98 return new PackageId(id.name, this, id.version, description); |
98 }); | 99 }); |
99 } | 100 } |
100 | 101 |
101 /// Ensure that the canonical clone of the repository referred to by [id] (the | 102 /// Ensure that the canonical clone of the repository referred to by [id] (the |
102 /// one in `<system cache>/git/cache`) exists and is up-to-date. Returns a | 103 /// one in `<system cache>/git/cache`) exists and is up-to-date. Returns a |
103 /// future that completes once this is finished and throws an exception if it | 104 /// future that completes once this is finished and throws an exception if it |
104 /// fails. | 105 /// fails. |
105 Future _ensureRepoCache(PackageId id) { | 106 Future _ensureRepoCache(PackageId id) { |
106 var path = _repoCachePath(id); | 107 var path = _repoCachePath(id); |
107 return exists(path).chain((exists) { | 108 return exists(path).then((exists) { |
108 if (!exists) return _clone(_getUrl(id), path, mirror: true); | 109 if (!exists) return _clone(_getUrl(id), path, mirror: true); |
109 | 110 |
110 return git.run(["fetch"], workingDir: path).transform((result) => null); | 111 return git.run(["fetch"], workingDir: path).then((result) => null); |
111 }); | 112 }); |
112 } | 113 } |
113 | 114 |
114 /// Returns a future that completes to the revision hash of [id]. | 115 /// Returns a future that completes to the revision hash of [id]. |
115 Future<String> _revisionAt(PackageId id) { | 116 Future<String> _revisionAt(PackageId id) { |
116 return git.run(["rev-parse", _getEffectiveRef(id)], | 117 return git.run(["rev-parse", _getEffectiveRef(id)], |
117 workingDir: _repoCachePath(id)).transform((result) => result[0]); | 118 workingDir: _repoCachePath(id)).then((result) => result[0]); |
118 } | 119 } |
119 | 120 |
120 /// Returns the path to the revision-specific cache of [id]. | 121 /// Returns the path to the revision-specific cache of [id]. |
121 Future<String> _revisionCachePath(PackageId id) { | 122 Future<String> _revisionCachePath(PackageId id) { |
122 return _revisionAt(id).transform((rev) { | 123 return _revisionAt(id).then((rev) { |
123 var revisionCacheName = '${id.name}-$rev'; | 124 var revisionCacheName = '${id.name}-$rev'; |
124 return join(systemCacheRoot, revisionCacheName); | 125 return join(systemCacheRoot, revisionCacheName); |
125 }); | 126 }); |
126 } | 127 } |
127 | 128 |
128 /// Clones the repo at the URI [from] to the path [to] on the local | 129 /// Clones the repo at the URI [from] to the path [to] on the local |
129 /// filesystem. | 130 /// filesystem. |
130 /// | 131 /// |
131 /// If [mirror] is true, create a bare, mirrored clone. This doesn't check out | 132 /// If [mirror] is true, create a bare, mirrored clone. This doesn't check out |
132 /// the working tree, but instead makes the repository a local mirror of the | 133 /// the working tree, but instead makes the repository a local mirror of the |
133 /// remote repository. See the manpage for `git clone` for more information. | 134 /// remote repository. See the manpage for `git clone` for more information. |
134 Future _clone(String from, String to, {bool mirror: false}) { | 135 Future _clone(String from, String to, {bool mirror: false}) { |
135 // Git on Windows does not seem to automatically create the destination | 136 // Git on Windows does not seem to automatically create the destination |
136 // directory. | 137 // directory. |
137 return ensureDir(to).chain((_) { | 138 return ensureDir(to).then((_) { |
138 var args = ["clone", from, to]; | 139 var args = ["clone", from, to]; |
139 if (mirror) args.insertRange(1, 1, "--mirror"); | 140 if (mirror) args.insertRange(1, 1, "--mirror"); |
140 return git.run(args); | 141 return git.run(args); |
141 }).transform((result) => null); | 142 }).then((result) => null); |
142 } | 143 } |
143 | 144 |
144 /// Checks out the reference [ref] in [repoPath]. | 145 /// Checks out the reference [ref] in [repoPath]. |
145 Future _checkOut(String repoPath, String ref) { | 146 Future _checkOut(String repoPath, String ref) { |
146 return git.run(["checkout", ref], workingDir: repoPath).transform( | 147 return git.run(["checkout", ref], workingDir: repoPath).then( |
147 (result) => null); | 148 (result) => null); |
148 } | 149 } |
149 | 150 |
150 /// Returns the path to the canonical clone of the repository referred to by | 151 /// Returns the path to the canonical clone of the repository referred to by |
151 /// [id] (the one in `<system cache>/git/cache`). | 152 /// [id] (the one in `<system cache>/git/cache`). |
152 String _repoCachePath(PackageId id) { | 153 String _repoCachePath(PackageId id) { |
153 var repoCacheName = '${id.name}-${sha1(_getUrl(id))}'; | 154 var repoCacheName = '${id.name}-${sha1(_getUrl(id))}'; |
154 return join(systemCacheRoot, 'cache', repoCacheName); | 155 return join(systemCacheRoot, 'cache', repoCacheName); |
155 } | 156 } |
156 | 157 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
190 return description['ref']; | 191 return description['ref']; |
191 } | 192 } |
192 | 193 |
193 /// Returns [description] if it's a description, or [PackageId.description] if | 194 /// Returns [description] if it's a description, or [PackageId.description] if |
194 /// it's a [PackageId]. | 195 /// it's a [PackageId]. |
195 _getDescription(description) { | 196 _getDescription(description) { |
196 if (description is PackageId) return description.description; | 197 if (description is PackageId) return description.description; |
197 return description; | 198 return description; |
198 } | 199 } |
199 } | 200 } |
OLD | NEW |