diff options
author | Fredrik de Groot <fredrik.de.groot@aptiv.com> | 2023-05-31 16:56:34 +0200 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2023-06-21 14:50:16 +0000 |
commit | be71c2f80fa115e8256211fd0e3116d93cfae93e (patch) | |
tree | 39769fb002ec24d520abde60cf6b0614c4c02558 | |
parent | 696e0c48a9de4d20f3de65bc014ca2991d16f041 (diff) | |
download | git-repo-be71c2f80fa115e8256211fd0e3116d93cfae93e.tar.gz |
manifest: enable remove-project using path
A something.xml that gets included by two different
files, that both remove and add same shared project
to two different locations, would not work
prior to this change.
Reason is that remove killed all name keys, even
though reuse of same repo in different locations
is allowed.
Solve by adding optional attrib path to
<remove-project name="foo" path="only_this_path" />
and tweak remove-project.
Behaves as before without path, and deletes
more selectively when remove path is supplied.
As secondary feature, a project can now also be removed
by only using path, assuming a matching project name
can be found.
Change-Id: I502d9f949f5d858ddc1503846b170473f76dc8e2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/375694
Tested-by: Fredrik de Groot <fredrik.de.groot@aptiv.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
-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>.""" |