diff options
| -rw-r--r-- | docs/manifest_submodule.txt | 130 | ||||
| -rw-r--r-- | manifest_loader.py | 3 | ||||
| -rw-r--r-- | manifest_submodule.py | 474 | ||||
| -rw-r--r-- | subcmds/init.py | 9 | ||||
| -rw-r--r-- | subcmds/manifest.py | 4 |
5 files changed, 619 insertions, 1 deletions
diff --git a/docs/manifest_submodule.txt b/docs/manifest_submodule.txt new file mode 100644 index 00000000..e7d1f643 --- /dev/null +++ b/docs/manifest_submodule.txt | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | repo Manifest Format (submodule) | ||
| 2 | ================================ | ||
| 3 | |||
| 4 | A repo manifest describes the structure of a repo client; that is | ||
| 5 | the directories that are visible and where they should be obtained | ||
| 6 | from with git. | ||
| 7 | |||
| 8 | The basic structure of a manifest is a bare Git repository holding | ||
| 9 | a 'gitmodules' file in the top level directory, and one or more | ||
| 10 | gitlink references pointing at commits from the referenced projects. | ||
| 11 | This is the same structure as used by 'git submodule'. | ||
| 12 | |||
| 13 | Manifests are inherently version controlled, since they are kept | ||
| 14 | within a Git repository. Updates to manifests are automatically | ||
| 15 | obtained by clients during `repo sync`. | ||
| 16 | |||
| 17 | .gitmodules | ||
| 18 | =========== | ||
| 19 | |||
| 20 | The '.gitmodules' file, located in the top-level directory of the | ||
| 21 | client's working tree (or manifest repository), is a text file with | ||
| 22 | a syntax matching the requirements of 'git config'. | ||
| 23 | |||
| 24 | This file contains one subsection per project (also called a | ||
| 25 | submodule by git), and the subsection value is a unique name to | ||
| 26 | describe the project. Each submodule section must contain the | ||
| 27 | following required keys: | ||
| 28 | |||
| 29 | * path | ||
| 30 | * url | ||
| 31 | |||
| 32 | submodule.<name>.path | ||
| 33 | --------------------- | ||
| 34 | |||
| 35 | Defines the path, relative to the top-level directory of the client's | ||
| 36 | working tree, where the project is expected to be checked out. The | ||
| 37 | path name must not end with a '/'. All paths must be unique within | ||
| 38 | the .gitmodules file. | ||
| 39 | |||
| 40 | At the specified path within the manifest repository a gitlink | ||
| 41 | tree entry (an entry with file mode 160000) must exist referencing | ||
| 42 | a commit SHA-1 from the project. This tree entry specifies the | ||
| 43 | exact version of the project that `repo sync` will synchronize the | ||
| 44 | client's working tree to. | ||
| 45 | |||
| 46 | submodule.<name>.url | ||
| 47 | -------------------- | ||
| 48 | |||
| 49 | Defines a URL from where the project repository can be cloned. | ||
| 50 | By default `repo sync` will clone from this URL whenever a user | ||
| 51 | needs to access this project. | ||
| 52 | |||
| 53 | submodule.<name>.revision | ||
| 54 | ------------------------- | ||
| 55 | |||
| 56 | Name of the branch in the project repository that Gerrit Code Review | ||
| 57 | should automatically refresh the project's gitlink entry from. | ||
| 58 | |||
| 59 | If set, during submit of a change within the referenced project, | ||
| 60 | Gerrit Code Review will automatically update the manifest | ||
| 61 | repository's corresponding gitlink to the new commit SHA-1 of | ||
| 62 | this branch. | ||
| 63 | |||
| 64 | Valid values are a short branch name (e.g. 'master'), a full ref | ||
| 65 | name (e.g. 'refs/heads/master'), or '.' to request using the same | ||
| 66 | branch name as the manifest branch itself. Since '.' automatically | ||
| 67 | uses the manifest branch, '.' is the recommended value. | ||
| 68 | |||
| 69 | If this key is not set, Gerrit Code Review will NOT automatically | ||
| 70 | update the gitlink. An unset key requires the manifest maintainer | ||
| 71 | to manually update the gitlink when it is necessary to reference | ||
| 72 | a different revision of the project. | ||
| 73 | |||
| 74 | submodule.<name>.update | ||
| 75 | ----------------------- | ||
| 76 | |||
| 77 | This key is not supported by repo. If set, it will be ignored. | ||
| 78 | |||
| 79 | .review | ||
| 80 | ======= | ||
| 81 | |||
| 82 | The optional '.review' file, located in the top-level directory of | ||
| 83 | the client's working tree (or manifest repository), is a text file | ||
| 84 | with a syntax matching the requirements of 'git config'. | ||
| 85 | |||
| 86 | This file describes how `repo upload` should interact with the | ||
| 87 | project's preferred code review system. | ||
| 88 | |||
| 89 | review.url | ||
| 90 | ---------- | ||
| 91 | |||
| 92 | URL of the default Gerrit Code Review server. If a project does | ||
| 93 | not have a specific URL in the '.review' file, this default URL | ||
| 94 | will be used instead. | ||
| 95 | |||
| 96 | review.<name>.url | ||
| 97 | ----------------- | ||
| 98 | |||
| 99 | Project specific URL of the Gerrit Code Review server, for the | ||
| 100 | submodule whose project name is <name>. | ||
| 101 | |||
| 102 | Example | ||
| 103 | ======= | ||
| 104 | |||
| 105 | $ cat .gitmodules | ||
| 106 | [submodule "app/Clock"] | ||
| 107 | path = clock | ||
| 108 | url = git://vcs.example.com/ClockWidget.git | ||
| 109 | revision = . | ||
| 110 | [submodule "app/Browser"] | ||
| 111 | path = net/browser | ||
| 112 | url = git://netgroup.example.com/network/web/Browser.git | ||
| 113 | revision = . | ||
| 114 | |||
| 115 | $ cat .review | ||
| 116 | [review] | ||
| 117 | url = vcs-gerrit.example.com | ||
| 118 | [review "app/Browser"] | ||
| 119 | url = netgroup.example.com | ||
| 120 | |||
| 121 | In the above example, the app/Clock project will send its code | ||
| 122 | reviews to the default server, vcs-gerrit.example.com, while | ||
| 123 | app/Browser will send its code reviews to netgroup.example.com. | ||
| 124 | |||
| 125 | See Also | ||
| 126 | ======== | ||
| 127 | |||
| 128 | * http://www.kernel.org/pub/software/scm/git/docs/gitmodules.html | ||
| 129 | * http://www.kernel.org/pub/software/scm/git/docs/git-config.html | ||
| 130 | * http://code.google.com/p/gerrit/ | ||
diff --git a/manifest_loader.py b/manifest_loader.py index 1ce1c1f3..467cb42a 100644 --- a/manifest_loader.py +++ b/manifest_loader.py | |||
| @@ -13,11 +13,14 @@ | |||
| 13 | # See the License for the specific language governing permissions and | 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. | 14 | # limitations under the License. |
| 15 | 15 | ||
| 16 | from manifest_submodule import SubmoduleManifest | ||
| 16 | from manifest_xml import XmlManifest | 17 | from manifest_xml import XmlManifest |
| 17 | 18 | ||
| 18 | def ParseManifest(repodir, type=None): | 19 | def ParseManifest(repodir, type=None): |
| 19 | if type: | 20 | if type: |
| 20 | return type(repodir) | 21 | return type(repodir) |
| 22 | if SubmoduleManifest.Is(repodir): | ||
| 23 | return SubmoduleManifest(repodir) | ||
| 21 | return XmlManifest(repodir) | 24 | return XmlManifest(repodir) |
| 22 | 25 | ||
| 23 | _manifest = None | 26 | _manifest = None |
diff --git a/manifest_submodule.py b/manifest_submodule.py new file mode 100644 index 00000000..92f187a0 --- /dev/null +++ b/manifest_submodule.py | |||
| @@ -0,0 +1,474 @@ | |||
| 1 | # | ||
| 2 | # Copyright (C) 2009 The Android Open Source Project | ||
| 3 | # | ||
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | # you may not use this file except in compliance with the License. | ||
| 6 | # You may obtain a copy of the License at | ||
| 7 | # | ||
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | # | ||
| 10 | # Unless required by applicable law or agreed to in writing, software | ||
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | # See the License for the specific language governing permissions and | ||
| 14 | # limitations under the License. | ||
| 15 | |||
| 16 | import sys | ||
| 17 | import os | ||
| 18 | import shutil | ||
| 19 | |||
| 20 | from error import GitError | ||
| 21 | from error import ManifestParseError | ||
| 22 | from git_command import GitCommand | ||
| 23 | from git_config import GitConfig | ||
| 24 | from git_config import IsId | ||
| 25 | from manifest import Manifest | ||
| 26 | from progress import Progress | ||
| 27 | from project import RemoteSpec | ||
| 28 | from project import Project | ||
| 29 | from project import MetaProject | ||
| 30 | from project import R_HEADS | ||
| 31 | from project import HEAD | ||
| 32 | from project import _lwrite | ||
| 33 | |||
| 34 | import manifest_xml | ||
| 35 | |||
| 36 | GITLINK = '160000' | ||
| 37 | |||
| 38 | def _rmdir(dir, top): | ||
| 39 | while dir != top: | ||
| 40 | try: | ||
| 41 | os.rmdir(dir) | ||
| 42 | except OSError: | ||
| 43 | break | ||
| 44 | dir = os.path.dirname(dir) | ||
| 45 | |||
| 46 | def _rmref(gitdir, ref): | ||
| 47 | os.remove(os.path.join(gitdir, ref)) | ||
| 48 | log = os.path.join(gitdir, 'logs', ref) | ||
| 49 | if os.path.exists(log): | ||
| 50 | os.remove(log) | ||
| 51 | _rmdir(os.path.dirname(log), gitdir) | ||
| 52 | |||
| 53 | def _has_gitmodules(d): | ||
| 54 | return os.path.exists(os.path.join(d, '.gitmodules')) | ||
| 55 | |||
| 56 | class SubmoduleManifest(Manifest): | ||
| 57 | """manifest from .gitmodules file""" | ||
| 58 | |||
| 59 | @classmethod | ||
| 60 | def Is(cls, repodir): | ||
| 61 | return _has_gitmodules(os.path.dirname(repodir)) \ | ||
| 62 | or _has_gitmodules(os.path.join(repodir, 'manifest')) \ | ||
| 63 | or _has_gitmodules(os.path.join(repodir, 'manifests')) | ||
| 64 | |||
| 65 | @classmethod | ||
| 66 | def IsBare(cls, p): | ||
| 67 | try: | ||
| 68 | p.bare_git.cat_file('-e', '%s:.gitmodules' % p.GetRevisionId()) | ||
| 69 | except GitError: | ||
| 70 | return False | ||
| 71 | return True | ||
| 72 | |||
| 73 | def __init__(self, repodir): | ||
| 74 | Manifest.__init__(self, repodir) | ||
| 75 | |||
| 76 | gitdir = os.path.join(repodir, 'manifest.git') | ||
| 77 | config = GitConfig.ForRepository(gitdir = gitdir) | ||
| 78 | |||
| 79 | if config.GetBoolean('repo.mirror'): | ||
| 80 | worktree = os.path.join(repodir, 'manifest') | ||
| 81 | relpath = None | ||
| 82 | else: | ||
| 83 | worktree = self.topdir | ||
| 84 | relpath = '.' | ||
| 85 | |||
| 86 | self.manifestProject = MetaProject(self, '__manifest__', | ||
| 87 | gitdir = gitdir, | ||
| 88 | worktree = worktree, | ||
| 89 | relpath = relpath) | ||
| 90 | self._modules = GitConfig(os.path.join(worktree, '.gitmodules'), | ||
| 91 | pickleFile = os.path.join( | ||
| 92 | repodir, '.repopickle_gitmodules' | ||
| 93 | )) | ||
| 94 | self._review = GitConfig(os.path.join(worktree, '.review'), | ||
| 95 | pickleFile = os.path.join( | ||
| 96 | repodir, '.repopickle_review' | ||
| 97 | )) | ||
| 98 | self._Unload() | ||
| 99 | |||
| 100 | @property | ||
| 101 | def projects(self): | ||
| 102 | self._Load() | ||
| 103 | return self._projects | ||
| 104 | |||
| 105 | def InitBranch(self): | ||
| 106 | m = self.manifestProject | ||
| 107 | if m.CurrentBranch is None: | ||
| 108 | b = m.revisionExpr | ||
| 109 | if b.startswith(R_HEADS): | ||
| 110 | b = b[len(R_HEADS):] | ||
| 111 | return m.StartBranch(b) | ||
| 112 | return True | ||
| 113 | |||
| 114 | def SetMRefs(self, project): | ||
| 115 | if project.revisionId is None: | ||
| 116 | # Special project, e.g. the manifest or repo executable. | ||
| 117 | # | ||
| 118 | return | ||
| 119 | |||
| 120 | ref = 'refs/remotes/m' | ||
| 121 | cur = project.bare_ref.get(ref) | ||
| 122 | exp = project.revisionId | ||
| 123 | if cur != exp: | ||
| 124 | msg = 'manifest set to %s' % exp | ||
| 125 | project.bare_git.UpdateRef(ref, exp, message = msg, detach = True) | ||
| 126 | |||
| 127 | ref = 'refs/remotes/m-revision' | ||
| 128 | cur = project.bare_ref.symref(ref) | ||
| 129 | exp = project.revisionExpr | ||
| 130 | if exp is None: | ||
| 131 | if cur: | ||
| 132 | _rmref(project.gitdir, ref) | ||
| 133 | elif cur != exp: | ||
| 134 | remote = project.GetRemote(project.remote.name) | ||
| 135 | dst = remote.ToLocal(exp) | ||
| 136 | msg = 'manifest set to %s (%s)' % (exp, dst) | ||
| 137 | project.bare_git.symbolic_ref('-m', msg, ref, dst) | ||
| 138 | |||
| 139 | def Upgrade_Local(self, old): | ||
| 140 | if isinstance(old, manifest_xml.XmlManifest): | ||
| 141 | self.FromXml_Local_1(old, checkout=True) | ||
| 142 | self.FromXml_Local_2(old) | ||
| 143 | else: | ||
| 144 | raise ManifestParseError, 'cannot upgrade manifest' | ||
| 145 | |||
| 146 | def FromXml_Local_1(self, old, checkout): | ||
| 147 | os.rename(old.manifestProject.gitdir, | ||
| 148 | os.path.join(old.repodir, 'manifest.git')) | ||
| 149 | |||
| 150 | oldmp = old.manifestProject | ||
| 151 | oldBranch = oldmp.CurrentBranch | ||
| 152 | b = oldmp.GetBranch(oldBranch).merge | ||
| 153 | if not b: | ||
| 154 | raise ManifestParseError, 'cannot upgrade manifest' | ||
| 155 | if b.startswith(R_HEADS): | ||
| 156 | b = b[len(R_HEADS):] | ||
| 157 | |||
| 158 | newmp = self.manifestProject | ||
| 159 | self._CleanOldMRefs(newmp) | ||
| 160 | if oldBranch != b: | ||
| 161 | newmp.bare_git.branch('-m', oldBranch, b) | ||
| 162 | newmp.config.ClearCache() | ||
| 163 | |||
| 164 | old_remote = newmp.GetBranch(b).remote.name | ||
| 165 | act_remote = self._GuessRemoteName(old) | ||
| 166 | if old_remote != act_remote: | ||
| 167 | newmp.bare_git.remote('rename', old_remote, act_remote) | ||
| 168 | newmp.config.ClearCache() | ||
| 169 | newmp.remote.name = act_remote | ||
| 170 | print >>sys.stderr, "Assuming remote named '%s'" % act_remote | ||
| 171 | |||
| 172 | if checkout: | ||
| 173 | for p in old.projects.values(): | ||
| 174 | for c in p.copyfiles: | ||
| 175 | if os.path.exists(c.abs_dest): | ||
| 176 | os.remove(c.abs_dest) | ||
| 177 | newmp._InitWorkTree() | ||
| 178 | else: | ||
| 179 | newmp._LinkWorkTree() | ||
| 180 | |||
| 181 | _lwrite(os.path.join(newmp.worktree,'.git',HEAD), | ||
| 182 | 'ref: refs/heads/%s\n' % b) | ||
| 183 | |||
| 184 | def _GuessRemoteName(self, old): | ||
| 185 | used = {} | ||
| 186 | for p in old.projects.values(): | ||
| 187 | n = p.remote.name | ||
| 188 | used[n] = used.get(n, 0) + 1 | ||
| 189 | |||
| 190 | remote_name = 'origin' | ||
| 191 | remote_used = 0 | ||
| 192 | for n in used.keys(): | ||
| 193 | if remote_used < used[n]: | ||
| 194 | remote_used = used[n] | ||
| 195 | remote_name = n | ||
| 196 | return remote_name | ||
| 197 | |||
| 198 | def FromXml_Local_2(self, old): | ||
| 199 | shutil.rmtree(old.manifestProject.worktree) | ||
| 200 | os.remove(old._manifestFile) | ||
| 201 | |||
| 202 | my_remote = self._Remote().name | ||
| 203 | new_base = os.path.join(self.repodir, 'projects') | ||
| 204 | old_base = os.path.join(self.repodir, 'projects.old') | ||
| 205 | os.rename(new_base, old_base) | ||
| 206 | os.makedirs(new_base) | ||
| 207 | |||
| 208 | info = [] | ||
| 209 | pm = Progress('Converting projects', len(self.projects)) | ||
| 210 | for p in self.projects.values(): | ||
| 211 | pm.update() | ||
| 212 | |||
| 213 | old_p = old.projects.get(p.name) | ||
| 214 | old_gitdir = os.path.join(old_base, '%s.git' % p.relpath) | ||
| 215 | if not os.path.isdir(old_gitdir): | ||
| 216 | continue | ||
| 217 | |||
| 218 | parent = os.path.dirname(p.gitdir) | ||
| 219 | if not os.path.isdir(parent): | ||
| 220 | os.makedirs(parent) | ||
| 221 | os.rename(old_gitdir, p.gitdir) | ||
| 222 | _rmdir(os.path.dirname(old_gitdir), self.repodir) | ||
| 223 | |||
| 224 | if not os.path.isdir(p.worktree): | ||
| 225 | os.makedirs(p.worktree) | ||
| 226 | |||
| 227 | if os.path.isdir(os.path.join(p.worktree, '.git')): | ||
| 228 | p._LinkWorkTree(relink=True) | ||
| 229 | |||
| 230 | self._CleanOldMRefs(p) | ||
| 231 | if old_p and old_p.remote.name != my_remote: | ||
| 232 | info.append("%s/: renamed remote '%s' to '%s'" \ | ||
| 233 | % (p.relpath, old_p.remote.name, my_remote)) | ||
| 234 | p.bare_git.remote('rename', old_p.remote.name, my_remote) | ||
| 235 | p.config.ClearCache() | ||
| 236 | |||
| 237 | self.SetMRefs(p) | ||
| 238 | pm.end() | ||
| 239 | for i in info: | ||
| 240 | print >>sys.stderr, i | ||
| 241 | |||
| 242 | def _CleanOldMRefs(self, p): | ||
| 243 | all_refs = p._allrefs | ||
| 244 | for ref in all_refs.keys(): | ||
| 245 | if ref.startswith(manifest_xml.R_M): | ||
| 246 | if p.bare_ref.symref(ref) != '': | ||
| 247 | _rmref(p.gitdir, ref) | ||
| 248 | else: | ||
| 249 | p.bare_git.DeleteRef(ref, all_refs[ref]) | ||
| 250 | |||
| 251 | def FromXml_Definition(self, old): | ||
| 252 | """Convert another manifest representation to this one. | ||
| 253 | """ | ||
| 254 | mp = self.manifestProject | ||
| 255 | gm = self._modules | ||
| 256 | gr = self._review | ||
| 257 | |||
| 258 | fd = open(os.path.join(mp.worktree, '.gitignore'), 'ab') | ||
| 259 | fd.write('/.repo\n') | ||
| 260 | fd.close() | ||
| 261 | |||
| 262 | sort_projects = list(old.projects.keys()) | ||
| 263 | sort_projects.sort() | ||
| 264 | |||
| 265 | b = mp.GetBranch(mp.CurrentBranch).merge | ||
| 266 | if b.startswith(R_HEADS): | ||
| 267 | b = b[len(R_HEADS):] | ||
| 268 | |||
| 269 | info = [] | ||
| 270 | pm = Progress('Converting manifest', len(sort_projects)) | ||
| 271 | for p in sort_projects: | ||
| 272 | pm.update() | ||
| 273 | p = old.projects[p] | ||
| 274 | |||
| 275 | gm.SetString('submodule.%s.path' % p.name, p.relpath) | ||
| 276 | gm.SetString('submodule.%s.url' % p.name, p.remote.url) | ||
| 277 | |||
| 278 | if gr.GetString('review.url') is None: | ||
| 279 | gr.SetString('review.url', p.remote.review) | ||
| 280 | elif gr.GetString('review.url') != p.remote.review: | ||
| 281 | gr.SetString('review.%s.url' % p.name, p.remote.review) | ||
| 282 | |||
| 283 | r = p.revisionExpr | ||
| 284 | if r and not IsId(r): | ||
| 285 | if r.startswith(R_HEADS): | ||
| 286 | r = r[len(R_HEADS):] | ||
| 287 | if r == b: | ||
| 288 | r = '.' | ||
| 289 | gm.SetString('submodule.%s.revision' % p.name, r) | ||
| 290 | |||
| 291 | for c in p.copyfiles: | ||
| 292 | info.append('Moved %s out of %s' % (c.src, p.relpath)) | ||
| 293 | c._Copy() | ||
| 294 | p.work_git.rm(c.src) | ||
| 295 | mp.work_git.add(c.dest) | ||
| 296 | |||
| 297 | self.SetRevisionId(p.relpath, p.GetRevisionId()) | ||
| 298 | mp.work_git.add('.gitignore', '.gitmodules', '.review') | ||
| 299 | pm.end() | ||
| 300 | for i in info: | ||
| 301 | print >>sys.stderr, i | ||
| 302 | |||
| 303 | def _Unload(self): | ||
| 304 | self._loaded = False | ||
| 305 | self._projects = {} | ||
| 306 | self._revisionIds = None | ||
| 307 | self.branch = None | ||
| 308 | |||
| 309 | def _Load(self): | ||
| 310 | if not self._loaded: | ||
| 311 | f = os.path.join(self.repodir, manifest_xml.LOCAL_MANIFEST_NAME) | ||
| 312 | if os.path.exists(f): | ||
| 313 | print >>sys.stderr, 'warning: ignoring %s' % f | ||
| 314 | |||
| 315 | m = self.manifestProject | ||
| 316 | b = m.CurrentBranch | ||
| 317 | if not b: | ||
| 318 | raise ManifestParseError, 'manifest cannot be on detached HEAD' | ||
| 319 | b = m.GetBranch(b).merge | ||
| 320 | if b.startswith(R_HEADS): | ||
| 321 | b = b[len(R_HEADS):] | ||
| 322 | self.branch = b | ||
| 323 | m.remote.name = self._Remote().name | ||
| 324 | |||
| 325 | self._ParseModules() | ||
| 326 | |||
| 327 | if self.IsMirror: | ||
| 328 | self._AddMetaProjectMirror(self.repoProject) | ||
| 329 | self._AddMetaProjectMirror(self.manifestProject) | ||
| 330 | |||
| 331 | self._loaded = True | ||
| 332 | |||
| 333 | def _ParseModules(self): | ||
| 334 | byPath = dict() | ||
| 335 | for name in self._modules.GetSubSections('submodule'): | ||
| 336 | p = self._ParseProject(name) | ||
| 337 | if self._projects.get(p.name): | ||
| 338 | raise ManifestParseError, 'duplicate project "%s"' % p.name | ||
| 339 | if byPath.get(p.relpath): | ||
| 340 | raise ManifestParseError, 'duplicate path "%s"' % p.relpath | ||
| 341 | self._projects[p.name] = p | ||
| 342 | byPath[p.relpath] = p | ||
| 343 | |||
| 344 | for relpath in self._allRevisionIds.keys(): | ||
| 345 | if relpath not in byPath: | ||
| 346 | raise ManifestParseError, \ | ||
| 347 | 'project "%s" not in .gitmodules' \ | ||
| 348 | % relpath | ||
| 349 | |||
| 350 | def _Remote(self): | ||
| 351 | m = self.manifestProject | ||
| 352 | b = m.GetBranch(m.CurrentBranch) | ||
| 353 | return b.remote | ||
| 354 | |||
| 355 | def _ResolveUrl(self, url): | ||
| 356 | if url.startswith('./') or url.startswith('../'): | ||
| 357 | base = self._Remote().url | ||
| 358 | try: | ||
| 359 | base = base[:base.rindex('/')+1] | ||
| 360 | except ValueError: | ||
| 361 | base = base[:base.rindex(':')+1] | ||
| 362 | if url.startswith('./'): | ||
| 363 | url = url[2:] | ||
| 364 | while '/' in base and url.startswith('../'): | ||
| 365 | base = base[:base.rindex('/')+1] | ||
| 366 | url = url[3:] | ||
| 367 | return base + url | ||
| 368 | return url | ||
| 369 | |||
| 370 | def _GetRevisionId(self, path): | ||
| 371 | return self._allRevisionIds.get(path) | ||
| 372 | |||
| 373 | @property | ||
| 374 | def _allRevisionIds(self): | ||
| 375 | if self._revisionIds is None: | ||
| 376 | a = dict() | ||
| 377 | p = GitCommand(self.manifestProject, | ||
| 378 | ['ls-files','-z','--stage'], | ||
| 379 | capture_stdout = True) | ||
| 380 | for line in p.process.stdout.read().split('\0')[:-1]: | ||
| 381 | l_info, l_path = line.split('\t', 2) | ||
| 382 | l_mode, l_id, l_stage = l_info.split(' ', 2) | ||
| 383 | if l_mode == GITLINK and l_stage == '0': | ||
| 384 | a[l_path] = l_id | ||
| 385 | p.Wait() | ||
| 386 | self._revisionIds = a | ||
| 387 | return self._revisionIds | ||
| 388 | |||
| 389 | def SetRevisionId(self, path, id): | ||
| 390 | self.manifestProject.work_git.update_index( | ||
| 391 | '--add','--cacheinfo', GITLINK, id, path) | ||
| 392 | |||
| 393 | def _ParseProject(self, name): | ||
| 394 | gm = self._modules | ||
| 395 | gr = self._review | ||
| 396 | |||
| 397 | path = gm.GetString('submodule.%s.path' % name) | ||
| 398 | if not path: | ||
| 399 | path = name | ||
| 400 | |||
| 401 | revId = self._GetRevisionId(path) | ||
| 402 | if not revId: | ||
| 403 | raise ManifestParseError( | ||
| 404 | 'submodule "%s" has no revision at "%s"' \ | ||
| 405 | % (name, path)) | ||
| 406 | |||
| 407 | url = gm.GetString('submodule.%s.url' % name) | ||
| 408 | if not url: | ||
| 409 | url = name | ||
| 410 | url = self._ResolveUrl(url) | ||
| 411 | |||
| 412 | review = gr.GetString('review.%s.url' % name) | ||
| 413 | if not review: | ||
| 414 | review = gr.GetString('review.url') | ||
| 415 | if not review: | ||
| 416 | review = self._Remote().review | ||
| 417 | |||
| 418 | remote = RemoteSpec(self._Remote().name, url, review) | ||
| 419 | revExpr = gm.GetString('submodule.%s.revision' % name) | ||
| 420 | if revExpr == '.': | ||
| 421 | revExpr = self.branch | ||
| 422 | |||
| 423 | if self.IsMirror: | ||
| 424 | relpath = None | ||
| 425 | worktree = None | ||
| 426 | gitdir = os.path.join(self.topdir, '%s.git' % name) | ||
| 427 | else: | ||
| 428 | worktree = os.path.join(self.topdir, path) | ||
| 429 | gitdir = os.path.join(self.repodir, 'projects/%s.git' % name) | ||
| 430 | |||
| 431 | return Project(manifest = self, | ||
| 432 | name = name, | ||
| 433 | remote = remote, | ||
| 434 | gitdir = gitdir, | ||
| 435 | worktree = worktree, | ||
| 436 | relpath = path, | ||
| 437 | revisionExpr = revExpr, | ||
| 438 | revisionId = revId) | ||
| 439 | |||
| 440 | def _AddMetaProjectMirror(self, m): | ||
| 441 | m_url = m.GetRemote(m.remote.name).url | ||
| 442 | if m_url.endswith('/.git'): | ||
| 443 | raise ManifestParseError, 'refusing to mirror %s' % m_url | ||
| 444 | |||
| 445 | name = self._GuessMetaName(m_url) | ||
| 446 | if name.endswith('.git'): | ||
| 447 | name = name[:-4] | ||
| 448 | |||
| 449 | if name not in self._projects: | ||
| 450 | m.PreSync() | ||
| 451 | gitdir = os.path.join(self.topdir, '%s.git' % name) | ||
| 452 | project = Project(manifest = self, | ||
| 453 | name = name, | ||
| 454 | remote = RemoteSpec(self._Remote().name, m_url), | ||
| 455 | gitdir = gitdir, | ||
| 456 | worktree = None, | ||
| 457 | relpath = None, | ||
| 458 | revisionExpr = m.revisionExpr, | ||
| 459 | revisionId = None) | ||
| 460 | self._projects[project.name] = project | ||
| 461 | |||
| 462 | def _GuessMetaName(self, m_url): | ||
| 463 | parts = m_url.split('/') | ||
| 464 | name = parts[-1] | ||
| 465 | parts = parts[0:-1] | ||
| 466 | s = len(parts) - 1 | ||
| 467 | while s > 0: | ||
| 468 | l = '/'.join(parts[0:s]) + '/' | ||
| 469 | r = '/'.join(parts[s:]) + '/' | ||
| 470 | for p in self._projects.values(): | ||
| 471 | if p.name.startswith(r) and p.remote.url.startswith(l): | ||
| 472 | return r + name | ||
| 473 | s -= 1 | ||
| 474 | return m_url[m_url.rindex('/') + 1:] | ||
diff --git a/subcmds/init.py b/subcmds/init.py index b5207fbf..cdbbfdf7 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
| @@ -21,6 +21,7 @@ from command import InteractiveCommand, MirrorSafeCommand | |||
| 21 | from error import ManifestParseError | 21 | from error import ManifestParseError |
| 22 | from project import SyncBuffer | 22 | from project import SyncBuffer |
| 23 | from git_command import git_require, MIN_GIT_VERSION | 23 | from git_command import git_require, MIN_GIT_VERSION |
| 24 | from manifest_submodule import SubmoduleManifest | ||
| 24 | from manifest_xml import XmlManifest | 25 | from manifest_xml import XmlManifest |
| 25 | from subcmds.sync import _ReloadManifest | 26 | from subcmds.sync import _ReloadManifest |
| 26 | 27 | ||
| @@ -144,6 +145,14 @@ to update the working directory files. | |||
| 144 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url | 145 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url |
| 145 | sys.exit(1) | 146 | sys.exit(1) |
| 146 | 147 | ||
| 148 | if is_new and SubmoduleManifest.IsBare(m): | ||
| 149 | new = self.GetManifest(reparse=True, type=SubmoduleManifest) | ||
| 150 | if m.gitdir != new.manifestProject.gitdir: | ||
| 151 | os.rename(m.gitdir, new.manifestProject.gitdir) | ||
| 152 | new = self.GetManifest(reparse=True, type=SubmoduleManifest) | ||
| 153 | m = new.manifestProject | ||
| 154 | self._ApplyOptions(opt, is_new) | ||
| 155 | |||
| 147 | if not is_new: | 156 | if not is_new: |
| 148 | # Force the manifest to load if it exists, the old graph | 157 | # Force the manifest to load if it exists, the old graph |
| 149 | # may be needed inside of _ReloadManifest(). | 158 | # may be needed inside of _ReloadManifest(). |
diff --git a/subcmds/manifest.py b/subcmds/manifest.py index 551b13bd..7a8b2ee8 100644 --- a/subcmds/manifest.py +++ b/subcmds/manifest.py | |||
| @@ -22,7 +22,7 @@ from manifest_xml import XmlManifest | |||
| 22 | def _doc(name): | 22 | def _doc(name): |
| 23 | r = os.path.dirname(__file__) | 23 | r = os.path.dirname(__file__) |
| 24 | r = os.path.dirname(r) | 24 | r = os.path.dirname(r) |
| 25 | fd = open(os.path.join(r, 'docs', 'manifest_xml.txt')) | 25 | fd = open(os.path.join(r, 'docs', name)) |
| 26 | try: | 26 | try: |
| 27 | return fd.read() | 27 | return fd.read() |
| 28 | finally: | 28 | finally: |
| @@ -48,6 +48,8 @@ in a Git repository for use during future 'repo init' invocations. | |||
| 48 | help = '' | 48 | help = '' |
| 49 | if isinstance(self.manifest, XmlManifest): | 49 | if isinstance(self.manifest, XmlManifest): |
| 50 | help += self._xmlHelp + '\n' + _doc('manifest_xml.txt') | 50 | help += self._xmlHelp + '\n' + _doc('manifest_xml.txt') |
| 51 | if isinstance(self.manifest, SubmoduleManifest): | ||
| 52 | help += _doc('manifest_submodule.txt') | ||
| 51 | return help | 53 | return help |
| 52 | 54 | ||
| 53 | def _Options(self, p): | 55 | def _Options(self, p): |
