summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFredrik de Groot <fredrik.de.groot@haleytek.com>2024-09-09 15:54:57 +0200
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2024-10-28 16:55:10 +0000
commit303bd963d57936873f62c7b61a885911afc46788 (patch)
tree6970ea441a7873ca08fd5af366a0bd0ba6b7a5ca
parentae384f8623aeb36809541a5e07900a77a0960d5f (diff)
downloadgit-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.md18
-rw-r--r--manifest_xml.py35
-rw-r--r--tests/test_manifest_xml.py85
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`.
433Attribute `upstream`: If specified, overrides the upstream of the original 435Attribute `upstream`: If specified, overrides the upstream of the original
434project. Same syntax as the corresponding element of `project`. 436project. Same syntax as the corresponding element of `project`.
435 437
438Attribute `base-rev`: If specified, adds a check against the revision
439to be extended. Manifest parse will fail and give a list of mismatch extends
440if the revisions being extended have changed since base-rev was set.
441Intended for use with layered manifests using hash revisions to prevent
442patch branches hiding newer upstream revisions. Also compares named refs
443like branches or tags but is misleading if branches are used as base-rev.
444Same syntax as the corresponding element of `project`.
445
436### Element annotation 446### Element annotation
437 447
438Zero or more annotation elements may be specified as children of a 448Zero or more annotation elements may be specified as children of a
@@ -496,6 +506,14 @@ name. Logic otherwise behaves like both are specified.
496Attribute `optional`: Set to true to ignore remove-project elements with no 506Attribute `optional`: Set to true to ignore remove-project elements with no
497matching `project` element. 507matching `project` element.
498 508
509Attribute `base-rev`: If specified, adds a check against the revision
510to be removed. Manifest parse will fail and give a list of mismatch removes
511if the revisions being removed have changed since base-rev was set.
512Intended for use with layered manifests using hash revisions to prevent
513patch branches hiding newer upstream revisions. Also compares named refs
514like branches or tags but is misleading if branches are used as base-rev.
515Same syntax as the corresponding element of `project`.
516
499### Element repo-hooks 517### Element repo-hooks
500 518
501NB: See the [practical documentation](./repo-hooks.md) for using repo hooks. 519NB: 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
1053class ExtendProjectElementTests(ManifestParseTestCase): 1138class ExtendProjectElementTests(ManifestParseTestCase):
1054 """Tests for <extend-project>.""" 1139 """Tests for <extend-project>."""