Skip to content

Commit 303bd96

Browse files
SpecificityD3luxeLUCI
authored andcommitted
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 <[email protected]> Tested-by: Fredrik de Groot <[email protected]> Commit-Queue: Josip Sokcevic <[email protected]>
1 parent ae384f8 commit 303bd96

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

docs/manifest-format.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,13 @@ following DTD:
107107
<!ATTLIST extend-project remote CDATA #IMPLIED>
108108
<!ATTLIST extend-project dest-branch CDATA #IMPLIED>
109109
<!ATTLIST extend-project upstream CDATA #IMPLIED>
110+
<!ATTLIST extend-project base-rev CDATA #IMPLIED>
110111

111112
<!ELEMENT remove-project EMPTY>
112113
<!ATTLIST remove-project name CDATA #IMPLIED>
113114
<!ATTLIST remove-project path CDATA #IMPLIED>
114115
<!ATTLIST remove-project optional CDATA #IMPLIED>
116+
<!ATTLIST remove-project base-rev CDATA #IMPLIED>
115117

116118
<!ELEMENT repo-hooks EMPTY>
117119
<!ATTLIST repo-hooks in-project CDATA #REQUIRED>
@@ -433,6 +435,14 @@ project. Same syntax as the corresponding element of `project`.
433435
Attribute `upstream`: If specified, overrides the upstream of the original
434436
project. Same syntax as the corresponding element of `project`.
435437

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+
436446
### Element annotation
437447

438448
Zero or more annotation elements may be specified as children of a
@@ -496,6 +506,14 @@ name. Logic otherwise behaves like both are specified.
496506
Attribute `optional`: Set to true to ignore remove-project elements with no
497507
matching `project` element.
498508

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+
499517
### Element repo-hooks
500518

501519
NB: See the [practical documentation](./repo-hooks.md) for using repo hooks.

manifest_xml.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,7 @@ def recursively_add_projects(project):
14451445

14461446
repo_hooks_project = None
14471447
enabled_repo_hooks = None
1448+
failed_revision_changes = []
14481449
for node in itertools.chain(*node_list):
14491450
if node.nodeName == "project":
14501451
project = self._ParseProject(node)
@@ -1471,6 +1472,7 @@ def recursively_add_projects(project):
14711472
remote = self._get_remote(node)
14721473
dest_branch = node.getAttribute("dest-branch")
14731474
upstream = node.getAttribute("upstream")
1475+
base_revision = node.getAttribute("base-rev")
14741476

14751477
named_projects = self._projects[name]
14761478
if dest_path and not path and len(named_projects) > 1:
@@ -1484,6 +1486,13 @@ def recursively_add_projects(project):
14841486
if groups:
14851487
p.groups.extend(groups)
14861488
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+
)
14871496
p.SetRevision(revision)
14881497

14891498
if remote_name:
@@ -1558,6 +1567,7 @@ def recursively_add_projects(project):
15581567
if node.nodeName == "remove-project":
15591568
name = node.getAttribute("name")
15601569
path = node.getAttribute("path")
1570+
base_revision = node.getAttribute("base-rev")
15611571

15621572
# Name or path needed.
15631573
if not name and not path:
@@ -1571,13 +1581,31 @@ def recursively_add_projects(project):
15711581
for projname, projects in list(self._projects.items()):
15721582
for p in projects:
15731583
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+
)
15741591
del self._paths[p.relpath]
15751592
if not removed_project:
15761593
del self._projects[name]
15771594
removed_project = name
15781595
elif path == p.relpath and (
15791596
name == projname or not name
15801597
):
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+
)
15811609
self._projects[projname].remove(p)
15821610
del self._paths[p.relpath]
15831611
removed_project = p.name
@@ -1597,6 +1625,13 @@ def recursively_add_projects(project):
15971625
"project: %s" % node.toxml()
15981626
)
15991627

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+
16001635
# Store repo hooks project information.
16011636
if repo_hooks_project:
16021637
# Store a reference to the Project.

tests/test_manifest_xml.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,91 @@ def test_remove_using_path_attrib(self):
10491049
self.assertTrue(found_proj1_path1)
10501050
self.assertTrue(found_proj2)
10511051

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+
10521137

10531138
class ExtendProjectElementTests(ManifestParseTestCase):
10541139
"""Tests for <extend-project>."""

0 commit comments

Comments
 (0)