summaryrefslogtreecommitdiffstats
path: root/manifest_xml.py
diff options
context:
space:
mode:
authorChe-Liang Chiou <clchiou@google.com>2012-01-11 11:28:42 +0800
committerChe-Liang Chiou <clchiou@google.com>2012-11-19 10:45:21 -0800
commitb2bd91c99b9435cf950ecf8efbb8439f31d3fcbc (patch)
tree5d26d3943317c11c1cd913fc5640074a5bc7910b /manifest_xml.py
parent3f5ea0b18207a81f58595b1a2e10e5ffb784b74f (diff)
downloadgit-repo-b2bd91c99b9435cf950ecf8efbb8439f31d3fcbc.tar.gz
Represent git-submodule as nested projects, take 2
(Previous submission of this change broke Android buildbot due to incorrect regular expression for parsing git-config output. During investigation, we also found that Android, which pulls Chromium, has a workaround for Chromium's submodules; its manifest includes Chromium's submodules. This new change, in addition to fixing the regex, also take this type of workarounds into consideration; it adds a new attribute that makes repo not fetch submodules unless submodules have a project element defined in the manifest, or this attribute is overridden by a parent project element or by the default element.) We need a representation of git-submodule in repo; otherwise repo will not sync submodules, and leave workspace in a broken state. Of course this will not be a problem if all projects are owned by the owner of the manifest file, who may simply choose not to use git-submodule in all projects. However, this is not possible in practice because manifest file owner is unlikely to own all upstream projects. As git submodules are simply git repositories, it is natural to treat them as plain repo projects that live inside a repo project. That is, we could use recursively declared projects to denote the is-submodule relation of git repositories. The behavior of repo remains the same to projects that do not have a sub-project within. As for parent projects, repo fetches them and their sub-projects as normal projects, and then checks out subprojects at the commit specified in parent's commit object. The sub-project is fetched at a path relative to parent project's working directory; so the path specified in manifest file should match that of .gitmodules file. If a submodule is not registered in repo manifest, repo will derive its properties from itself and its parent project, which might not always be correct. In such cases, the subproject is called a derived subproject. To a user, a sub-project is merely a git-submodule; so all tips of working with a git-submodule apply here, too. For example, you should not run `repo sync` in a parent repository if its submodule is dirty. Change-Id: I4b8344c1b9ccad2f58ad304573133e5d52e1faef
Diffstat (limited to 'manifest_xml.py')
-rw-r--r--manifest_xml.py129
1 files changed, 103 insertions, 26 deletions
diff --git a/manifest_xml.py b/manifest_xml.py
index 122393cf..36f8ef87 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -40,6 +40,7 @@ class _Default(object):
40 remote = None 40 remote = None
41 sync_j = 1 41 sync_j = 1
42 sync_c = False 42 sync_c = False
43 sync_s = False
43 44
44class _XmlRemote(object): 45class _XmlRemote(object):
45 def __init__(self, 46 def __init__(self,
@@ -178,6 +179,9 @@ class XmlManifest(object):
178 if d.sync_c: 179 if d.sync_c:
179 have_default = True 180 have_default = True
180 e.setAttribute('sync-c', 'true') 181 e.setAttribute('sync-c', 'true')
182 if d.sync_s:
183 have_default = True
184 e.setAttribute('sync-s', 'true')
181 if have_default: 185 if have_default:
182 root.appendChild(e) 186 root.appendChild(e)
183 root.appendChild(doc.createTextNode('')) 187 root.appendChild(doc.createTextNode(''))
@@ -188,20 +192,25 @@ class XmlManifest(object):
188 root.appendChild(e) 192 root.appendChild(e)
189 root.appendChild(doc.createTextNode('')) 193 root.appendChild(doc.createTextNode(''))
190 194
191 sort_projects = list(self.projects.keys()) 195 def output_projects(parent, parent_node, projects):
192 sort_projects.sort() 196 for p in projects:
193 197 output_project(parent, parent_node, self.projects[p])
194 for p in sort_projects:
195 p = self.projects[p]
196 198
199 def output_project(parent, parent_node, p):
197 if not p.MatchesGroups(groups): 200 if not p.MatchesGroups(groups):
198 continue 201 return
202
203 name = p.name
204 relpath = p.relpath
205 if parent:
206 name = self._UnjoinName(parent.name, name)
207 relpath = self._UnjoinRelpath(parent.relpath, relpath)
199 208
200 e = doc.createElement('project') 209 e = doc.createElement('project')
201 root.appendChild(e) 210 parent_node.appendChild(e)
202 e.setAttribute('name', p.name) 211 e.setAttribute('name', name)
203 if p.relpath != p.name: 212 if relpath != name:
204 e.setAttribute('path', p.relpath) 213 e.setAttribute('path', relpath)
205 if not d.remote or p.remote.name != d.remote.name: 214 if not d.remote or p.remote.name != d.remote.name:
206 e.setAttribute('remote', p.remote.name) 215 e.setAttribute('remote', p.remote.name)
207 if peg_rev: 216 if peg_rev:
@@ -239,6 +248,19 @@ class XmlManifest(object):
239 if p.sync_c: 248 if p.sync_c:
240 e.setAttribute('sync-c', 'true') 249 e.setAttribute('sync-c', 'true')
241 250
251 if p.sync_s:
252 e.setAttribute('sync-s', 'true')
253
254 if p.subprojects:
255 sort_projects = [subp.name for subp in p.subprojects]
256 sort_projects.sort()
257 output_projects(p, e, sort_projects)
258
259 sort_projects = [key for key in self.projects.keys()
260 if not self.projects[key].parent]
261 sort_projects.sort()
262 output_projects(None, root, sort_projects)
263
242 if self._repo_hooks_project: 264 if self._repo_hooks_project:
243 root.appendChild(doc.createTextNode('')) 265 root.appendChild(doc.createTextNode(''))
244 e = doc.createElement('repo-hooks') 266 e = doc.createElement('repo-hooks')
@@ -409,14 +431,19 @@ class XmlManifest(object):
409 (self.manifestFile)) 431 (self.manifestFile))
410 self._manifest_server = url 432 self._manifest_server = url
411 433
434 def recursively_add_projects(project):
435 if self._projects.get(project.name):
436 raise ManifestParseError(
437 'duplicate project %s in %s' %
438 (project.name, self.manifestFile))
439 self._projects[project.name] = project
440 for subproject in project.subprojects:
441 recursively_add_projects(subproject)
442
412 for node in itertools.chain(*node_list): 443 for node in itertools.chain(*node_list):
413 if node.nodeName == 'project': 444 if node.nodeName == 'project':
414 project = self._ParseProject(node) 445 project = self._ParseProject(node)
415 if self._projects.get(project.name): 446 recursively_add_projects(project)
416 raise ManifestParseError(
417 'duplicate project %s in %s' %
418 (project.name, self.manifestFile))
419 self._projects[project.name] = project
420 if node.nodeName == 'repo-hooks': 447 if node.nodeName == 'repo-hooks':
421 # Get the name of the project and the (space-separated) list of enabled. 448 # Get the name of the project and the (space-separated) list of enabled.
422 repo_hooks_project = self._reqatt(node, 'in-project') 449 repo_hooks_project = self._reqatt(node, 'in-project')
@@ -524,6 +551,12 @@ class XmlManifest(object):
524 d.sync_c = False 551 d.sync_c = False
525 else: 552 else:
526 d.sync_c = sync_c.lower() in ("yes", "true", "1") 553 d.sync_c = sync_c.lower() in ("yes", "true", "1")
554
555 sync_s = node.getAttribute('sync-s')
556 if not sync_s:
557 d.sync_s = False
558 else:
559 d.sync_s = sync_s.lower() in ("yes", "true", "1")
527 return d 560 return d
528 561
529 def _ParseNotice(self, node): 562 def _ParseNotice(self, node):
@@ -565,11 +598,19 @@ class XmlManifest(object):
565 598
566 return '\n'.join(cleanLines) 599 return '\n'.join(cleanLines)
567 600
568 def _ParseProject(self, node): 601 def _JoinName(self, parent_name, name):
602 return os.path.join(parent_name, name)
603
604 def _UnjoinName(self, parent_name, name):
605 return os.path.relpath(name, parent_name)
606
607 def _ParseProject(self, node, parent = None):
569 """ 608 """
570 reads a <project> element from the manifest file 609 reads a <project> element from the manifest file
571 """ 610 """
572 name = self._reqatt(node, 'name') 611 name = self._reqatt(node, 'name')
612 if parent:
613 name = self._JoinName(parent.name, name)
573 614
574 remote = self._get_remote(node) 615 remote = self._get_remote(node)
575 if remote is None: 616 if remote is None:
@@ -607,6 +648,12 @@ class XmlManifest(object):
607 else: 648 else:
608 sync_c = sync_c.lower() in ("yes", "true", "1") 649 sync_c = sync_c.lower() in ("yes", "true", "1")
609 650
651 sync_s = node.getAttribute('sync-s')
652 if not sync_s:
653 sync_s = self._default.sync_s
654 else:
655 sync_s = sync_s.lower() in ("yes", "true", "1")
656
610 upstream = node.getAttribute('upstream') 657 upstream = node.getAttribute('upstream')
611 658
612 groups = '' 659 groups = ''
@@ -614,37 +661,67 @@ class XmlManifest(object):
614 groups = node.getAttribute('groups') 661 groups = node.getAttribute('groups')
615 groups = [x for x in re.split(r'[,\s]+', groups) if x] 662 groups = [x for x in re.split(r'[,\s]+', groups) if x]
616 663
617 default_groups = ['all', 'name:%s' % name, 'path:%s' % path] 664 if parent is None:
618 groups.extend(set(default_groups).difference(groups)) 665 relpath, worktree, gitdir = self.GetProjectPaths(name, path)
619
620 if self.IsMirror:
621 worktree = None
622 gitdir = os.path.join(self.topdir, '%s.git' % name)
623 else: 666 else:
624 worktree = os.path.join(self.topdir, path).replace('\\', '/') 667 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
625 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path) 668
669 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
670 groups.extend(set(default_groups).difference(groups))
626 671
627 project = Project(manifest = self, 672 project = Project(manifest = self,
628 name = name, 673 name = name,
629 remote = remote.ToRemoteSpec(name), 674 remote = remote.ToRemoteSpec(name),
630 gitdir = gitdir, 675 gitdir = gitdir,
631 worktree = worktree, 676 worktree = worktree,
632 relpath = path, 677 relpath = relpath,
633 revisionExpr = revisionExpr, 678 revisionExpr = revisionExpr,
634 revisionId = None, 679 revisionId = None,
635 rebase = rebase, 680 rebase = rebase,
636 groups = groups, 681 groups = groups,
637 sync_c = sync_c, 682 sync_c = sync_c,
638 upstream = upstream) 683 sync_s = sync_s,
684 upstream = upstream,
685 parent = parent)
639 686
640 for n in node.childNodes: 687 for n in node.childNodes:
641 if n.nodeName == 'copyfile': 688 if n.nodeName == 'copyfile':
642 self._ParseCopyFile(project, n) 689 self._ParseCopyFile(project, n)
643 if n.nodeName == 'annotation': 690 if n.nodeName == 'annotation':
644 self._ParseAnnotation(project, n) 691 self._ParseAnnotation(project, n)
692 if n.nodeName == 'project':
693 project.subprojects.append(self._ParseProject(n, parent = project))
645 694
646 return project 695 return project
647 696
697 def GetProjectPaths(self, name, path):
698 relpath = path
699 if self.IsMirror:
700 worktree = None
701 gitdir = os.path.join(self.topdir, '%s.git' % name)
702 else:
703 worktree = os.path.join(self.topdir, path).replace('\\', '/')
704 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
705 return relpath, worktree, gitdir
706
707 def GetSubprojectName(self, parent, submodule_path):
708 return os.path.join(parent.name, submodule_path)
709
710 def _JoinRelpath(self, parent_relpath, relpath):
711 return os.path.join(parent_relpath, relpath)
712
713 def _UnjoinRelpath(self, parent_relpath, relpath):
714 return os.path.relpath(relpath, parent_relpath)
715
716 def GetSubprojectPaths(self, parent, path):
717 relpath = self._JoinRelpath(parent.relpath, path)
718 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
719 if self.IsMirror:
720 worktree = None
721 else:
722 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
723 return relpath, worktree, gitdir
724
648 def _ParseCopyFile(self, project, node): 725 def _ParseCopyFile(self, project, node):
649 src = self._reqatt(node, 'src') 726 src = self._reqatt(node, 'src')
650 dest = self._reqatt(node, 'dest') 727 dest = self._reqatt(node, 'dest')