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: - - + + + @@ -473,7 +474,7 @@ of the repo client. ### Element remove-project -Deletes the named project from the internal manifest table, possibly +Deletes a project from the internal manifest table, possibly allowing a subsequent project element in the same manifest file to replace the project with a different source. @@ -481,6 +482,17 @@ This element is mostly useful in a local manifest file, where the user can remove a project, and possibly replace it with their own definition. +The project `name` or project `path` can be used to specify the remove target +meaning one of them is required. If only name is specified, all +projects with that name are removed. + +If both name and path are specified, only projects with the same name and +path are removed, meaning projects with the same name but in other +locations are kept. + +If only path is specified, a matching project is removed regardless of its +name. Logic otherwise behaves like both are specified. + Attribute `optional`: Set to true to ignore remove-project elements with no matching `project` element. 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 self._contactinfo = ContactInfo(bugurl) if node.nodeName == "remove-project": - name = self._reqatt(node, "name") + name = node.getAttribute("name") + path = node.getAttribute("path") - if name in self._projects: - for p in self._projects[name]: - del self._paths[p.relpath] - del self._projects[name] + # Name or path needed. + if not name and not path: + raise ManifestParseError( + "remove-project must have name and/or path" + ) - # If the manifest removes the hooks project, treat it as if - # it deleted - # the repo-hooks element too. - if repo_hooks_project == name: - repo_hooks_project = None - elif not XmlBool(node, "optional", False): + removed_project = "" + + # Find and remove projects based on name and/or path. + for projname, projects in list(self._projects.items()): + for p in projects: + if name == projname and not path: + del self._paths[p.relpath] + if not removed_project: + del self._projects[name] + removed_project = name + elif path == p.relpath and ( + name == projname or not name + ): + self._projects[projname].remove(p) + del self._paths[p.relpath] + removed_project = p.name + + # If the manifest removes the hooks project, treat it as if + # it deleted the repo-hooks element too. + if ( + removed_project + and removed_project not in self._projects + and repo_hooks_project == removed_project + ): + repo_hooks_project = None + + if not removed_project and not XmlBool(node, "optional", False): raise ManifestParseError( "remove-project element specifies non-existent " - "project: %s" % name + "project: %s" % node.toxml() ) # 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): ) self.assertEqual(manifest.projects, []) + def test_remove_using_path_attrib(self): + manifest = self.getXmlManifest( + """ + + + + + + + + + + + + + + + + + + +""" + ) + found_proj1_path1 = False + found_proj2 = False + for proj in manifest.projects: + if proj.name == "project1": + found_proj1_path1 = True + self.assertEqual(proj.relpath, "tests/path1") + if proj.name == "project2": + found_proj2 = True + self.assertNotEqual(proj.name, "project3") + self.assertNotEqual(proj.name, "project4") + self.assertNotEqual(proj.name, "project5") + self.assertNotEqual(proj.name, "project6") + self.assertTrue(found_proj1_path1) + self.assertTrue(found_proj2) + class ExtendProjectElementTests(ManifestParseTestCase): """Tests for ."""