diff --git a/docs/manifest-format.md b/docs/manifest-format.md
index 06d370a3e..d1a11cc99 100644
--- a/docs/manifest-format.md
+++ b/docs/manifest-format.md
@@ -566,6 +566,7 @@ These restrictions are not enforced for [Local Manifests].
Attribute `groups`: List of additional groups to which all projects
in the included manifest belong. This appends and recurses, meaning
all projects in included manifests carry all parent include groups.
+This also applies to all extend-project elements in the included manifests.
Same syntax as the corresponding element of `project`.
Attribute `revision`: Name of a Git branch (e.g. `main` or `refs/heads/main`)
diff --git a/man/repo-manifest.1 b/man/repo-manifest.1
index 4658b1e92..df3943ceb 100644
--- a/man/repo-manifest.1
+++ b/man/repo-manifest.1
@@ -627,7 +627,8 @@ restrictions are not enforced for [Local Manifests].
.PP
Attribute `groups`: List of additional groups to which all projects in the
included manifest belong. This appends and recurses, meaning all projects in
-included manifests carry all parent include groups. Same syntax as the
+included manifests carry all parent include groups. This also applies to all
+extend\-project elements in the included manifests. Same syntax as the
corresponding element of `project`.
.PP
Attribute `revision`: Name of a Git branch (e.g. `main` or `refs/heads/main`)
diff --git a/manifest_xml.py b/manifest_xml.py
index 5e0b53b99..33a552827 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -1335,7 +1335,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
f"failed parsing included manifest {name}: {e}"
)
else:
- if parent_groups and node.nodeName == "project":
+ if parent_groups and node.nodeName in (
+ "project",
+ "extend-project",
+ ):
nodeGroups = parent_groups
if node.hasAttribute("groups"):
nodeGroups = (
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py
index c1fed2bf9..d4bf76a91 100644
--- a/tests/test_manifest_xml.py
+++ b/tests/test_manifest_xml.py
@@ -488,6 +488,41 @@ class IncludeElementTests(ManifestParseTestCase):
# Check level2 proj group not removed.
self.assertIn("l2g1", proj.groups)
+ def test_group_levels_with_extend_project(self):
+ root_m = self.manifest_dir / "root.xml"
+ root_m.write_text(
+ """
+
+
+
+
+
+
+"""
+ )
+ (self.manifest_dir / "man1.xml").write_text(
+ """
+
+
+
+"""
+ )
+ (self.manifest_dir / "man2.xml").write_text(
+ """
+
+
+
+"""
+ )
+ include_m = manifest_xml.XmlManifest(str(self.repodir), str(root_m))
+ proj = include_m.projects[0]
+ # Check project has inherited group via project element.
+ self.assertIn("top-group1", proj.groups)
+ # Check project has inherited group via extend-project element.
+ self.assertIn("top-group2", proj.groups)
+ # Check project has set group via extend-project element.
+ self.assertIn("eg1", proj.groups)
+
def test_allow_bad_name_from_user(self):
"""Check handling of bad name attribute from the user's input."""