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:
-
+
+
]>
```
@@ -368,6 +369,10 @@ target manifest to include - it must be a usable manifest on its own.
Attribute `name`: the manifest to include, specified relative to
the manifest repository's root.
+Attribute `groups`: List of additional groups to which all projects
+in the included manifest belong. This appends and recurses, meaning
+all projects in sub-manifests carry all parent include groups.
+Same syntax as the corresponding element of `project`.
## Local Manifests
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
self._loaded = True
- def _ParseManifestXml(self, path, include_root):
+ def _ParseManifestXml(self, path, include_root, parent_groups=''):
try:
root = xml.dom.minidom.parse(path)
except (OSError, xml.parsers.expat.ExpatError) as e:
@@ -656,12 +656,17 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
for node in manifest.childNodes:
if node.nodeName == 'include':
name = self._reqatt(node, 'name')
+ include_groups = ''
+ if parent_groups:
+ include_groups = parent_groups
+ if node.hasAttribute('groups'):
+ include_groups = node.getAttribute('groups') + ',' + include_groups
fp = os.path.join(include_root, name)
if not os.path.isfile(fp):
raise ManifestParseError("include %s doesn't exist or isn't a file"
% (name,))
try:
- nodes.extend(self._ParseManifestXml(fp, include_root))
+ nodes.extend(self._ParseManifestXml(fp, include_root, include_groups))
# should isolate this to the exact exception, but that's
# tricky. actual parsing implementation may vary.
except (KeyboardInterrupt, RuntimeError, SystemExit):
@@ -670,6 +675,11 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
raise ManifestParseError(
"failed parsing included manifest %s: %s" % (name, e))
else:
+ if parent_groups and node.nodeName == 'project':
+ nodeGroups = parent_groups
+ if node.hasAttribute('groups'):
+ nodeGroups = node.getAttribute('groups') + ',' + nodeGroups
+ node.setAttribute('groups', nodeGroups)
nodes.append(node)
return nodes
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):
self.assertCountEqual(
result['extras'],
['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
+
+ def test_include_levels(self):
+ root_m = os.path.join(self.manifest_dir, 'root.xml')
+ with open(root_m, 'w') as fp:
+ fp.write("""
+
+
+
+
+
+
+
+""")
+ with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
+ fp.write("""
+
+
+
+
+""")
+ with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
+ fp.write("""
+
+
+
+""")
+ include_m = manifest_xml.XmlManifest(self.repodir, root_m)
+ for proj in include_m.projects:
+ if proj.name == 'root-name1':
+ # Check include group not set on root level proj.
+ self.assertNotIn('level1-group', proj.groups)
+ if proj.name == 'root-name2':
+ # Check root proj group not removed.
+ self.assertIn('r2g1', proj.groups)
+ if proj.name == 'level1-name1':
+ # Check level1 proj has inherited group level 1.
+ self.assertIn('level1-group', proj.groups)
+ if proj.name == 'level2-name1':
+ # Check level2 proj has inherited group levels 1 and 2.
+ self.assertIn('level1-group', proj.groups)
+ self.assertIn('level2-group', proj.groups)
+ # Check level2 proj group not removed.
+ self.assertIn('l2g1', proj.groups)