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>.""" |
