summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/manifest-format.md7
-rw-r--r--manifest_xml.py14
-rw-r--r--tests/test_manifest_xml.py43
3 files changed, 61 insertions, 3 deletions
diff --git a/docs/manifest-format.md b/docs/manifest-format.md
index b35a065f..2af34ac2 100644
--- a/docs/manifest-format.md
+++ b/docs/manifest-format.md
@@ -99,7 +99,8 @@ following DTD:
99 <!ATTLIST repo-hooks enabled-list CDATA #REQUIRED> 99 <!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
100 100
101 <!ELEMENT include EMPTY> 101 <!ELEMENT include EMPTY>
102 <!ATTLIST include name CDATA #REQUIRED> 102 <!ATTLIST include name CDATA #REQUIRED>
103 <!ATTLIST include groups CDATA #IMPLIED>
103]> 104]>
104``` 105```
105 106
@@ -368,6 +369,10 @@ target manifest to include - it must be a usable manifest on its own.
368Attribute `name`: the manifest to include, specified relative to 369Attribute `name`: the manifest to include, specified relative to
369the manifest repository's root. 370the manifest repository's root.
370 371
372Attribute `groups`: List of additional groups to which all projects
373in the included manifest belong. This appends and recurses, meaning
374all projects in sub-manifests carry all parent include groups.
375Same syntax as the corresponding element of `project`.
371 376
372## Local Manifests 377## Local Manifests
373 378
diff --git a/manifest_xml.py b/manifest_xml.py
index 95c67d73..ad0017cc 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -637,7 +637,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
637 637
638 self._loaded = True 638 self._loaded = True
639 639
640 def _ParseManifestXml(self, path, include_root): 640 def _ParseManifestXml(self, path, include_root, parent_groups=''):
641 try: 641 try:
642 root = xml.dom.minidom.parse(path) 642 root = xml.dom.minidom.parse(path)
643 except (OSError, xml.parsers.expat.ExpatError) as e: 643 except (OSError, xml.parsers.expat.ExpatError) as e:
@@ -656,12 +656,17 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
656 for node in manifest.childNodes: 656 for node in manifest.childNodes:
657 if node.nodeName == 'include': 657 if node.nodeName == 'include':
658 name = self._reqatt(node, 'name') 658 name = self._reqatt(node, 'name')
659 include_groups = ''
660 if parent_groups:
661 include_groups = parent_groups
662 if node.hasAttribute('groups'):
663 include_groups = node.getAttribute('groups') + ',' + include_groups
659 fp = os.path.join(include_root, name) 664 fp = os.path.join(include_root, name)
660 if not os.path.isfile(fp): 665 if not os.path.isfile(fp):
661 raise ManifestParseError("include %s doesn't exist or isn't a file" 666 raise ManifestParseError("include %s doesn't exist or isn't a file"
662 % (name,)) 667 % (name,))
663 try: 668 try:
664 nodes.extend(self._ParseManifestXml(fp, include_root)) 669 nodes.extend(self._ParseManifestXml(fp, include_root, include_groups))
665 # should isolate this to the exact exception, but that's 670 # should isolate this to the exact exception, but that's
666 # tricky. actual parsing implementation may vary. 671 # tricky. actual parsing implementation may vary.
667 except (KeyboardInterrupt, RuntimeError, SystemExit): 672 except (KeyboardInterrupt, RuntimeError, SystemExit):
@@ -670,6 +675,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
670 raise ManifestParseError( 675 raise ManifestParseError(
671 "failed parsing included manifest %s: %s" % (name, e)) 676 "failed parsing included manifest %s: %s" % (name, e))
672 else: 677 else:
678 if parent_groups and node.nodeName == 'project':
679 nodeGroups = parent_groups
680 if node.hasAttribute('groups'):
681 nodeGroups = node.getAttribute('groups') + ',' + nodeGroups
682 node.setAttribute('groups', nodeGroups)
673 nodes.append(node) 683 nodes.append(node)
674 return nodes 684 return nodes
675 685
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py
index 40385cce..939717be 100644
--- a/tests/test_manifest_xml.py
+++ b/tests/test_manifest_xml.py
@@ -235,3 +235,46 @@ class XmlManifestTests(unittest.TestCase):
235 self.assertCountEqual( 235 self.assertCountEqual(
236 result['extras'], 236 result['extras'],
237 ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path']) 237 ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
238
239 def test_include_levels(self):
240 root_m = os.path.join(self.manifest_dir, 'root.xml')
241 with open(root_m, 'w') as fp:
242 fp.write("""
243<manifest>
244 <remote name="test-remote" fetch="http://localhost" />
245 <default remote="test-remote" revision="refs/heads/main" />
246 <include name="level1.xml" groups="level1-group" />
247 <project name="root-name1" path="root-path1" />
248 <project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
249</manifest>
250""")
251 with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
252 fp.write("""
253<manifest>
254 <include name="level2.xml" groups="level2-group" />
255 <project name="level1-name1" path="level1-path1" />
256</manifest>
257""")
258 with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
259 fp.write("""
260<manifest>
261 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
262</manifest>
263""")
264 include_m = manifest_xml.XmlManifest(self.repodir, root_m)
265 for proj in include_m.projects:
266 if proj.name == 'root-name1':
267 # Check include group not set on root level proj.
268 self.assertNotIn('level1-group', proj.groups)
269 if proj.name == 'root-name2':
270 # Check root proj group not removed.
271 self.assertIn('r2g1', proj.groups)
272 if proj.name == 'level1-name1':
273 # Check level1 proj has inherited group level 1.
274 self.assertIn('level1-group', proj.groups)
275 if proj.name == 'level2-name1':
276 # Check level2 proj has inherited group levels 1 and 2.
277 self.assertIn('level1-group', proj.groups)
278 self.assertIn('level2-group', proj.groups)
279 # Check level2 proj group not removed.
280 self.assertIn('l2g1', proj.groups)