diff options
author | Fredrik de Groot <fredrik.de.groot@haleytek.com> | 2024-09-09 15:54:57 +0200 |
---|---|---|
committer | LUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2024-10-28 16:55:10 +0000 |
commit | 303bd963d57936873f62c7b61a885911afc46788 (patch) | |
tree | 6970ea441a7873ca08fd5af366a0bd0ba6b7a5ca | |
parent | ae384f8623aeb36809541a5e07900a77a0960d5f (diff) | |
download | git-repo-303bd963d57936873f62c7b61a885911afc46788.tar.gz |
manifest: add optional base check on remove and extend
This adds an optional, built-in checker for
guarding against patches hanging on wrong
base revisions, which is useful if a lower layer of
the manifest changes after a patch was done.
When adding a patch with a new revision using
extend-project or remove-project/project:
C---D---E patches in project bla
/
A---B project bla in manifest state 1
<extend-project name="bla" revision="E" base-rev="B">
If project bla gets updated, in a new snap ID
or by a supplier or similar, to a new state:
C---D---E patches in project bla
/
A---B---F---G project bla in manifest state 2
Parsing will fail because revision of bla is now G,
giving the choice to create a new patch branch
from G and updating base-rev, or keeping previous
branch for some reason and only updating base-rev.
Intended for use in a layered manifest with
hashed revisions. Named refs like branches and tags
also work fine when comparing, but will be misleading
if a branch is used as base-rev.
Change-Id: Ic6211550a7d3cc9656057f6a2087c505b40cad2b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/436777
Reviewed-by: Josip Sokcevic <sokcevic@google.com>
Tested-by: Fredrik de Groot <fredrik.de.groot@haleytek.com>
Commit-Queue: Josip Sokcevic <sokcevic@google.com>
-rw-r--r-- | docs/manifest-format.md | 18 | ||||
-rw-r--r-- | manifest_xml.py | 35 | ||||
-rw-r--r-- | tests/test_manifest_xml.py | 85 |
3 files changed, 138 insertions, 0 deletions
diff --git a/docs/manifest-format.md b/docs/manifest-format.md index 36dae6de..cfb80164 100644 --- a/docs/manifest-format.md +++ b/docs/manifest-format.md | |||
@@ -107,11 +107,13 @@ following DTD: | |||
107 | <!ATTLIST extend-project remote CDATA #IMPLIED> | 107 | <!ATTLIST extend-project remote CDATA #IMPLIED> |
108 | <!ATTLIST extend-project dest-branch CDATA #IMPLIED> | 108 | <!ATTLIST extend-project dest-branch CDATA #IMPLIED> |
109 | <!ATTLIST extend-project upstream CDATA #IMPLIED> | 109 | <!ATTLIST extend-project upstream CDATA #IMPLIED> |
110 | <!ATTLIST extend-project base-rev CDATA #IMPLIED> | ||
110 | 111 | ||
111 | <!ELEMENT remove-project EMPTY> | 112 | <!ELEMENT remove-project EMPTY> |
112 | <!ATTLIST remove-project name CDATA #IMPLIED> | 113 | <!ATTLIST remove-project name CDATA #IMPLIED> |
113 | <!ATTLIST remove-project path CDATA #IMPLIED> | 114 | <!ATTLIST remove-project path CDATA #IMPLIED> |
114 | <!ATTLIST remove-project optional CDATA #IMPLIED> | 115 | <!ATTLIST remove-project optional CDATA #IMPLIED> |
116 | <!ATTLIST remove-project base-rev CDATA #IMPLIED> | ||
115 | 117 | ||
116 | <!ELEMENT repo-hooks EMPTY> | 118 | <!ELEMENT repo-hooks EMPTY> |
117 | <!ATTLIST repo-hooks in-project CDATA #REQUIRED> | 119 | <!ATTLIST repo-hooks in-project CDATA #REQUIRED> |
@@ -433,6 +435,14 @@ project. Same syntax as the corresponding element of `project`. | |||
433 | Attribute `upstream`: If specified, overrides the upstream of the original | 435 | Attribute `upstream`: If specified, overrides the upstream of the original |
434 | project. Same syntax as the corresponding element of `project`. | 436 | project. Same syntax as the corresponding element of `project`. |
435 | 437 | ||
438 | Attribute `base-rev`: If specified, adds a check against the revision | ||
439 | to be extended. Manifest parse will fail and give a list of mismatch extends | ||
440 | if the revisions being extended have changed since base-rev was set. | ||
441 | Intended for use with layered manifests using hash revisions to prevent | ||
442 | patch branches hiding newer upstream revisions. Also compares named refs | ||
443 | like branches or tags but is misleading if branches are used as base-rev. | ||
444 | Same syntax as the corresponding element of `project`. | ||
445 | |||
436 | ### Element annotation | 446 | ### Element annotation |
437 | 447 | ||
438 | Zero or more annotation elements may be specified as children of a | 448 | Zero or more annotation elements may be specified as children of a |
@@ -496,6 +506,14 @@ name. Logic otherwise behaves like both are specified. | |||
496 | Attribute `optional`: Set to true to ignore remove-project elements with no | 506 | Attribute `optional`: Set to true to ignore remove-project elements with no |
497 | matching `project` element. | 507 | matching `project` element. |
498 | 508 | ||
509 | Attribute `base-rev`: If specified, adds a check against the revision | ||
510 | to be removed. Manifest parse will fail and give a list of mismatch removes | ||
511 | if the revisions being removed have changed since base-rev was set. | ||
512 | Intended for use with layered manifests using hash revisions to prevent | ||
513 | patch branches hiding newer upstream revisions. Also compares named refs | ||
514 | like branches or tags but is misleading if branches are used as base-rev. | ||
515 | Same syntax as the corresponding element of `project`. | ||
516 | |||
499 | ### Element repo-hooks | 517 | ### Element repo-hooks |
500 | 518 | ||
501 | NB: See the [practical documentation](./repo-hooks.md) for using repo hooks. | 519 | NB: See the [practical documentation](./repo-hooks.md) for using repo hooks. |
diff --git a/manifest_xml.py b/manifest_xml.py index b26b0cac..cdab31e6 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -1445,6 +1445,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1445 | 1445 | ||
1446 | repo_hooks_project = None | 1446 | repo_hooks_project = None |
1447 | enabled_repo_hooks = None | 1447 | enabled_repo_hooks = None |
1448 | failed_revision_changes = [] | ||
1448 | for node in itertools.chain(*node_list): | 1449 | for node in itertools.chain(*node_list): |
1449 | if node.nodeName == "project": | 1450 | if node.nodeName == "project": |
1450 | project = self._ParseProject(node) | 1451 | project = self._ParseProject(node) |
@@ -1471,6 +1472,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1471 | remote = self._get_remote(node) | 1472 | remote = self._get_remote(node) |
1472 | dest_branch = node.getAttribute("dest-branch") | 1473 | dest_branch = node.getAttribute("dest-branch") |
1473 | upstream = node.getAttribute("upstream") | 1474 | upstream = node.getAttribute("upstream") |
1475 | base_revision = node.getAttribute("base-rev") | ||
1474 | 1476 | ||
1475 | named_projects = self._projects[name] | 1477 | named_projects = self._projects[name] |
1476 | if dest_path and not path and len(named_projects) > 1: | 1478 | if dest_path and not path and len(named_projects) > 1: |
@@ -1484,6 +1486,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1484 | if groups: | 1486 | if groups: |
1485 | p.groups.extend(groups) | 1487 | p.groups.extend(groups) |
1486 | if revision: | 1488 | if revision: |
1489 | if base_revision: | ||
1490 | if p.revisionExpr != base_revision: | ||
1491 | failed_revision_changes.append( | ||
1492 | "extend-project name %s mismatch base " | ||
1493 | "%s vs revision %s" | ||
1494 | % (name, base_revision, p.revisionExpr) | ||
1495 | ) | ||
1487 | p.SetRevision(revision) | 1496 | p.SetRevision(revision) |
1488 | 1497 | ||
1489 | if remote_name: | 1498 | if remote_name: |
@@ -1558,6 +1567,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1558 | if node.nodeName == "remove-project": | 1567 | if node.nodeName == "remove-project": |
1559 | name = node.getAttribute("name") | 1568 | name = node.getAttribute("name") |
1560 | path = node.getAttribute("path") | 1569 | path = node.getAttribute("path") |
1570 | base_revision = node.getAttribute("base-rev") | ||
1561 | 1571 | ||
1562 | # Name or path needed. | 1572 | # Name or path needed. |
1563 | if not name and not path: | 1573 | if not name and not path: |
@@ -1571,6 +1581,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1571 | for projname, projects in list(self._projects.items()): | 1581 | for projname, projects in list(self._projects.items()): |
1572 | for p in projects: | 1582 | for p in projects: |
1573 | if name == projname and not path: | 1583 | if name == projname and not path: |
1584 | if base_revision: | ||
1585 | if p.revisionExpr != base_revision: | ||
1586 | failed_revision_changes.append( | ||
1587 | "remove-project name %s mismatch base " | ||
1588 | "%s vs revision %s" | ||
1589 | % (name, base_revision, p.revisionExpr) | ||
1590 | ) | ||
1574 | del self._paths[p.relpath] | 1591 | del self._paths[p.relpath] |
1575 | if not removed_project: | 1592 | if not removed_project: |
1576 | del self._projects[name] | 1593 | del self._projects[name] |
@@ -1578,6 +1595,17 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1578 | elif path == p.relpath and ( | 1595 | elif path == p.relpath and ( |
1579 | name == projname or not name | 1596 | name == projname or not name |
1580 | ): | 1597 | ): |
1598 | if base_revision: | ||
1599 | if p.revisionExpr != base_revision: | ||
1600 | failed_revision_changes.append( | ||
1601 | "remove-project path %s mismatch base " | ||
1602 | "%s vs revision %s" | ||
1603 | % ( | ||
1604 | p.relpath, | ||
1605 | base_revision, | ||
1606 | p.revisionExpr, | ||
1607 | ) | ||
1608 | ) | ||
1581 | self._projects[projname].remove(p) | 1609 | self._projects[projname].remove(p) |
1582 | del self._paths[p.relpath] | 1610 | del self._paths[p.relpath] |
1583 | removed_project = p.name | 1611 | removed_project = p.name |
@@ -1597,6 +1625,13 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
1597 | "project: %s" % node.toxml() | 1625 | "project: %s" % node.toxml() |
1598 | ) | 1626 | ) |
1599 | 1627 | ||
1628 | if failed_revision_changes: | ||
1629 | raise ManifestParseError( | ||
1630 | "revision base check failed, rebase patches and update " | ||
1631 | "base revs for: ", | ||
1632 | failed_revision_changes, | ||
1633 | ) | ||
1634 | |||
1600 | # Store repo hooks project information. | 1635 | # Store repo hooks project information. |
1601 | if repo_hooks_project: | 1636 | if repo_hooks_project: |
1602 | # Store a reference to the Project. | 1637 | # Store a reference to the Project. |
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py index 3f03272a..3d1fde96 100644 --- a/tests/test_manifest_xml.py +++ b/tests/test_manifest_xml.py | |||
@@ -1049,6 +1049,91 @@ class RemoveProjectElementTests(ManifestParseTestCase): | |||
1049 | self.assertTrue(found_proj1_path1) | 1049 | self.assertTrue(found_proj1_path1) |
1050 | self.assertTrue(found_proj2) | 1050 | self.assertTrue(found_proj2) |
1051 | 1051 | ||
1052 | def test_base_revision_checks_on_patching(self): | ||
1053 | manifest_fail_wrong_tag = self.getXmlManifest( | ||
1054 | """ | ||
1055 | <manifest> | ||
1056 | <remote name="default-remote" fetch="http://localhost" /> | ||
1057 | <default remote="default-remote" revision="tag.002" /> | ||
1058 | <project name="project1" path="tests/path1" /> | ||
1059 | <extend-project name="project1" revision="new_hash" base-rev="tag.001" /> | ||
1060 | </manifest> | ||
1061 | """ | ||
1062 | ) | ||
1063 | with self.assertRaises(error.ManifestParseError): | ||
1064 | manifest_fail_wrong_tag.ToXml() | ||
1065 | |||
1066 | manifest_fail_remove = self.getXmlManifest( | ||
1067 | """ | ||
1068 | <manifest> | ||
1069 | <remote name="default-remote" fetch="http://localhost" /> | ||
1070 | <default remote="default-remote" revision="refs/heads/main" /> | ||
1071 | <project name="project1" path="tests/path1" revision="hash1" /> | ||
1072 | <remove-project name="project1" base-rev="wrong_hash" /> | ||
1073 | </manifest> | ||
1074 | """ | ||
1075 | ) | ||
1076 | with self.assertRaises(error.ManifestParseError): | ||
1077 | manifest_fail_remove.ToXml() | ||
1078 | |||
1079 | manifest_fail_extend = self.getXmlManifest( | ||
1080 | """ | ||
1081 | <manifest> | ||
1082 | <remote name="default-remote" fetch="http://localhost" /> | ||
1083 | <default remote="default-remote" revision="refs/heads/main" /> | ||
1084 | <project name="project1" path="tests/path1" revision="hash1" /> | ||
1085 | <extend-project name="project1" revision="new_hash" base-rev="wrong_hash" /> | ||
1086 | </manifest> | ||
1087 | """ | ||
1088 | ) | ||
1089 | with self.assertRaises(error.ManifestParseError): | ||
1090 | manifest_fail_extend.ToXml() | ||
1091 | |||
1092 | manifest_fail_unknown = self.getXmlManifest( | ||
1093 | """ | ||
1094 | <manifest> | ||
1095 | <remote name="default-remote" fetch="http://localhost" /> | ||
1096 | <default remote="default-remote" revision="refs/heads/main" /> | ||
1097 | <project name="project1" path="tests/path1" /> | ||
1098 | <extend-project name="project1" revision="new_hash" base-rev="any_hash" /> | ||
1099 | </manifest> | ||
1100 | """ | ||
1101 | ) | ||
1102 | with self.assertRaises(error.ManifestParseError): | ||
1103 | manifest_fail_unknown.ToXml() | ||
1104 | |||
1105 | manifest_ok = self.getXmlManifest( | ||
1106 | """ | ||
1107 | <manifest> | ||
1108 | <remote name="default-remote" fetch="http://localhost" /> | ||
1109 | <default remote="default-remote" revision="refs/heads/main" /> | ||
1110 | <project name="project1" path="tests/path1" revision="hash1" /> | ||
1111 | <project name="project2" path="tests/path2" revision="hash2" /> | ||
1112 | <project name="project3" path="tests/path3" revision="hash3" /> | ||
1113 | <project name="project4" path="tests/path4" revision="hash4" /> | ||
1114 | |||
1115 | <remove-project name="project1" /> | ||
1116 | <remove-project name="project2" base-rev="hash2" /> | ||
1117 | <project name="project2" path="tests/path2" revision="new_hash2" /> | ||
1118 | <extend-project name="project3" base-rev="hash3" revision="new_hash3" /> | ||
1119 | <extend-project name="project3" base-rev="new_hash3" revision="newer_hash3" /> | ||
1120 | <remove-project path="tests/path4" base-rev="hash4" /> | ||
1121 | </manifest> | ||
1122 | """ | ||
1123 | ) | ||
1124 | found_proj2 = False | ||
1125 | found_proj3 = False | ||
1126 | for proj in manifest_ok.projects: | ||
1127 | if proj.name == "project2": | ||
1128 | found_proj2 = True | ||
1129 | if proj.name == "project3": | ||
1130 | found_proj3 = True | ||
1131 | self.assertNotEqual(proj.name, "project1") | ||
1132 | self.assertNotEqual(proj.name, "project4") | ||
1133 | self.assertTrue(found_proj2) | ||
1134 | self.assertTrue(found_proj3) | ||
1135 | self.assertTrue(len(manifest_ok.projects) == 2) | ||
1136 | |||
1052 | 1137 | ||
1053 | class ExtendProjectElementTests(ManifestParseTestCase): | 1138 | class ExtendProjectElementTests(ManifestParseTestCase): |
1054 | """Tests for <extend-project>.""" | 1139 | """Tests for <extend-project>.""" |