diff options
-rw-r--r-- | docs/manifest-format.md | 18 | ||||
-rw-r--r-- | manifest_xml.py | 49 | ||||
-rw-r--r-- | tests/test_manifest_xml.py | 38 |
3 files changed, 89 insertions, 16 deletions
diff --git a/docs/manifest-format.md b/docs/manifest-format.md index edcb28cb..36dae6de 100644 --- a/docs/manifest-format.md +++ b/docs/manifest-format.md | |||
@@ -109,8 +109,9 @@ following DTD: | |||
109 | <!ATTLIST extend-project upstream CDATA #IMPLIED> | 109 | <!ATTLIST extend-project upstream CDATA #IMPLIED> |
110 | 110 | ||
111 | <!ELEMENT remove-project EMPTY> | 111 | <!ELEMENT remove-project EMPTY> |
112 | <!ATTLIST remove-project name CDATA #REQUIRED> | 112 | <!ATTLIST remove-project name CDATA #IMPLIED> |
113 | <!ATTLIST remove-project optional CDATA #IMPLIED> | 113 | <!ATTLIST remove-project path CDATA #IMPLIED> |
114 | <!ATTLIST remove-project optional CDATA #IMPLIED> | ||
114 | 115 | ||
115 | <!ELEMENT repo-hooks EMPTY> | 116 | <!ELEMENT repo-hooks EMPTY> |
116 | <!ATTLIST repo-hooks in-project CDATA #REQUIRED> | 117 | <!ATTLIST repo-hooks in-project CDATA #REQUIRED> |
@@ -473,7 +474,7 @@ of the repo client. | |||
473 | 474 | ||
474 | ### Element remove-project | 475 | ### Element remove-project |
475 | 476 | ||
476 | Deletes the named project from the internal manifest table, possibly | 477 | Deletes a project from the internal manifest table, possibly |
477 | allowing a subsequent project element in the same manifest file to | 478 | allowing a subsequent project element in the same manifest file to |
478 | replace the project with a different source. | 479 | replace the project with a different source. |
479 | 480 | ||
@@ -481,6 +482,17 @@ This element is mostly useful in a local manifest file, where | |||
481 | the user can remove a project, and possibly replace it with their | 482 | the user can remove a project, and possibly replace it with their |
482 | own definition. | 483 | own definition. |
483 | 484 | ||
485 | The project `name` or project `path` can be used to specify the remove target | ||
486 | meaning one of them is required. If only name is specified, all | ||
487 | projects with that name are removed. | ||
488 | |||
489 | If both name and path are specified, only projects with the same name and | ||
490 | path are removed, meaning projects with the same name but in other | ||
491 | locations are kept. | ||
492 | |||
493 | If only path is specified, a matching project is removed regardless of its | ||
494 | name. Logic otherwise behaves like both are specified. | ||
495 | |||
484 | Attribute `optional`: Set to true to ignore remove-project elements with no | 496 | Attribute `optional`: Set to true to ignore remove-project elements with no |
485 | matching `project` element. | 497 | matching `project` element. |
486 | 498 | ||
diff --git a/manifest_xml.py b/manifest_xml.py index 555bf736..73be1b6e 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -1535,22 +1535,45 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1535 | self._contactinfo = ContactInfo(bugurl) | 1535 | self._contactinfo = ContactInfo(bugurl) |
1536 | 1536 | ||
1537 | if node.nodeName == "remove-project": | 1537 | if node.nodeName == "remove-project": |
1538 | name = self._reqatt(node, "name") | 1538 | name = node.getAttribute("name") |
1539 | path = node.getAttribute("path") | ||
1539 | 1540 | ||
1540 | if name in self._projects: | 1541 | # Name or path needed. |
1541 | for p in self._projects[name]: | 1542 | if not name and not path: |
1542 | del self._paths[p.relpath] | 1543 | raise ManifestParseError( |
1543 | del self._projects[name] | 1544 | "remove-project must have name and/or path" |
1544 | 1545 | ) | |
1545 | # If the manifest removes the hooks project, treat it as if | 1546 | |
1546 | # it deleted | 1547 | removed_project = "" |
1547 | # the repo-hooks element too. | 1548 | |
1548 | if repo_hooks_project == name: | 1549 | # Find and remove projects based on name and/or path. |
1549 | repo_hooks_project = None | 1550 | for projname, projects in list(self._projects.items()): |
1550 | elif not XmlBool(node, "optional", False): | 1551 | for p in projects: |
1552 | if name == projname and not path: | ||
1553 | del self._paths[p.relpath] | ||
1554 | if not removed_project: | ||
1555 | del self._projects[name] | ||
1556 | removed_project = name | ||
1557 | elif path == p.relpath and ( | ||
1558 | name == projname or not name | ||
1559 | ): | ||
1560 | self._projects[projname].remove(p) | ||
1561 | del self._paths[p.relpath] | ||
1562 | removed_project = p.name | ||
1563 | |||
1564 | # If the manifest removes the hooks project, treat it as if | ||
1565 | # it deleted the repo-hooks element too. | ||
1566 | if ( | ||
1567 | removed_project | ||
1568 | and removed_project not in self._projects | ||
1569 | and repo_hooks_project == removed_project | ||
1570 | ): | ||
1571 | repo_hooks_project = None | ||
1572 | |||
1573 | if not removed_project and not XmlBool(node, "optional", False): | ||
1551 | raise ManifestParseError( | 1574 | raise ManifestParseError( |
1552 | "remove-project element specifies non-existent " | 1575 | "remove-project element specifies non-existent " |
1553 | "project: %s" % name | 1576 | "project: %s" % node.toxml() |
1554 | ) | 1577 | ) |
1555 | 1578 | ||
1556 | # Store repo hooks project information. | 1579 | # Store repo hooks project information. |
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py index ef511055..1015e114 100644 --- a/tests/test_manifest_xml.py +++ b/tests/test_manifest_xml.py | |||
@@ -996,6 +996,44 @@ class RemoveProjectElementTests(ManifestParseTestCase): | |||
996 | ) | 996 | ) |
997 | self.assertEqual(manifest.projects, []) | 997 | self.assertEqual(manifest.projects, []) |
998 | 998 | ||
999 | def test_remove_using_path_attrib(self): | ||
1000 | manifest = self.getXmlManifest( | ||
1001 | """ | ||
1002 | <manifest> | ||
1003 | <remote name="default-remote" fetch="http://localhost" /> | ||
1004 | <default remote="default-remote" revision="refs/heads/main" /> | ||
1005 | <project name="project1" path="tests/path1" /> | ||
1006 | <project name="project1" path="tests/path2" /> | ||
1007 | <project name="project2" /> | ||
1008 | <project name="project3" /> | ||
1009 | <project name="project4" path="tests/path3" /> | ||
1010 | <project name="project4" path="tests/path4" /> | ||
1011 | <project name="project5" /> | ||
1012 | <project name="project6" path="tests/path6" /> | ||
1013 | |||
1014 | <remove-project name="project1" path="tests/path2" /> | ||
1015 | <remove-project name="project3" /> | ||
1016 | <remove-project name="project4" /> | ||
1017 | <remove-project path="project5" /> | ||
1018 | <remove-project path="tests/path6" /> | ||
1019 | </manifest> | ||
1020 | """ | ||
1021 | ) | ||
1022 | found_proj1_path1 = False | ||
1023 | found_proj2 = False | ||
1024 | for proj in manifest.projects: | ||
1025 | if proj.name == "project1": | ||
1026 | found_proj1_path1 = True | ||
1027 | self.assertEqual(proj.relpath, "tests/path1") | ||
1028 | if proj.name == "project2": | ||
1029 | found_proj2 = True | ||
1030 | self.assertNotEqual(proj.name, "project3") | ||
1031 | self.assertNotEqual(proj.name, "project4") | ||
1032 | self.assertNotEqual(proj.name, "project5") | ||
1033 | self.assertNotEqual(proj.name, "project6") | ||
1034 | self.assertTrue(found_proj1_path1) | ||
1035 | self.assertTrue(found_proj2) | ||
1036 | |||
999 | 1037 | ||
1000 | class ExtendProjectElementTests(ManifestParseTestCase): | 1038 | class ExtendProjectElementTests(ManifestParseTestCase): |
1001 | """Tests for <extend-project>.""" | 1039 | """Tests for <extend-project>.""" |