Skip to content

Commit 22195cf

Browse files
committed
ci_scripts: check_patch.py: add
Migrate check_patch.py from the wheel_builder repository, adding a new RISE copyright and updating the description. Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
1 parent ac8dfb3 commit 22195cf

1 file changed

Lines changed: 146 additions & 0 deletions

File tree

ci_scripts/check_patch.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# SPDX-FileCopyrightText: 2025 BayLibre, SAS
2+
# SPDX-FileCopyrightText: 2026 The RISE Project
3+
4+
# SPDX-License-Identifier: Apache-2.0
5+
"""
6+
This script is based on the one located at:
7+
8+
https://gitlab.com/riseproject/python/wheel_builder/-/blob/main/ci_scripts/check_patch.py
9+
10+
Its purpose is to check the contents of patches (commits) submitted to the
11+
project for aan "Upstream-Status" tag, which is inspired by the same tag used
12+
in the Yocto Project:
13+
14+
https://docs.yoctoproject.org/dev/contributor-guide/recipe-style-guide.html#patch-upstream-status
15+
16+
See the valid_statuses dictionary below for types of Upstream-Status tags
17+
accepted.
18+
"""
19+
20+
import sys
21+
import subprocess
22+
import re
23+
24+
valid_statuses = {
25+
"Issue": {
26+
"value": r"^\[https?://\S+\]$", # Mandatory url in brackets
27+
"hint": "[url]",
28+
"mandatory": True,
29+
"description": "Brackets enclosed url of the issue opened on upstream project",
30+
},
31+
"Submitted": {
32+
"value": r"^\[https?://\S+\]$", # Mandatory url in brackets
33+
"hint": "[url]",
34+
"mandatory": True,
35+
"description": "Brackets enclosed url of the merge request opened on upstream project",
36+
},
37+
"To upstream": {
38+
"value": r"^(\[.*\])?$", # Optional comment in brackets
39+
"hint": "[comment]",
40+
"mandatory": False,
41+
"description": "Optional brackets enclosed comment to add any useful information for future upstream submission",
42+
},
43+
"Inappropriate": {
44+
"value": r"^\[.+\]$", # Any comment in brackets
45+
"hint": "[comment]",
46+
"mandatory": True,
47+
"description": "Brackets enclosed reason why the patch is inappropriate for upstream",
48+
},
49+
"Backport": {
50+
"value": r"^\[https?://\S+\]$", # Mandatory url in brackets
51+
"hint": "[url]",
52+
"mandatory": True,
53+
"description": "Brackets enclosed url of the backported patch",
54+
},
55+
}
56+
57+
58+
def get_commits_between(start_ref, end_ref):
59+
"""Get commit hashes between two branches or commits."""
60+
cmd = ["git", "rev-list", f"{start_ref}..{end_ref}"]
61+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
62+
return result.stdout.splitlines()
63+
64+
65+
def get_commit_files(commit):
66+
"""Get a list of patch files added or modified in a commit."""
67+
cmd = ["git", "diff-tree", "--no-commit-id", "--name-status", "-r", commit]
68+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
69+
files = []
70+
for line in result.stdout.splitlines():
71+
status, filename = line.split("\t", 1)
72+
if status in ("A", "M") and filename.endswith(".patch"):
73+
files.append(filename)
74+
return files
75+
76+
77+
def extract_patch_from_commit(commit, patch_file):
78+
"""Extract a patch file from a given commit."""
79+
cmd = ["git", "show", f"{commit}:{patch_file}"]
80+
result = subprocess.run(cmd, capture_output=True, text=True)
81+
if result.returncode != 0:
82+
print(f"WARNING: Could not extract {patch_file} from commit {commit}")
83+
return None
84+
return result.stdout
85+
86+
87+
def check_upstream_status(patch_content, patch):
88+
"""Check if the Upstream-Status tag is correctly formatted in a patch file."""
89+
90+
status_key = "|".join(valid_statuses.keys())
91+
match = re.search(r"^Upstream-Status: *(.*)$", patch_content, re.MULTILINE)
92+
93+
if not match:
94+
print(f"❌Missing Upstream-Status tag in {patch}")
95+
return False
96+
97+
value = match.groups()
98+
99+
match = re.search(rf"^({status_key}|\w+) *(.*)$", value[0], re.MULTILINE)
100+
101+
status, extra = match.groups()
102+
103+
if status not in valid_statuses.keys():
104+
print(f"❌Invalid Upstream-Status '{status}' in {patch}")
105+
return False
106+
107+
if not re.match(valid_statuses[status]["value"], extra):
108+
print(f"❌Incorrect format for Upstream-Status '{status}' in {patch}")
109+
return False
110+
111+
return True
112+
113+
114+
def main():
115+
if len(sys.argv) != 3:
116+
print("Usage: python script.py <start_branch_or_commit> <end_branch_or_commit>")
117+
sys.exit(1)
118+
119+
start_ref, end_ref = sys.argv[1], sys.argv[2]
120+
commits = get_commits_between(start_ref, end_ref)
121+
122+
error_found = False
123+
124+
for commit in commits:
125+
patch_files = get_commit_files(commit)
126+
for patch in patch_files:
127+
patch_content = extract_patch_from_commit(commit, patch)
128+
if patch_content:
129+
if not check_upstream_status(patch_content, patch):
130+
error_found = True
131+
else:
132+
print(f"✅{patch}")
133+
134+
if error_found:
135+
print("Valid formats:")
136+
for key, value in valid_statuses.items():
137+
print(f" - Upstream-Status: {key} {value["hint"]}")
138+
print(
139+
f" {value["hint"]}: {value["description"]} {'(mandatory)' if value["mandatory"] else ''}"
140+
)
141+
sys.exit(1)
142+
sys.exit(0)
143+
144+
145+
if __name__ == "__main__":
146+
main()

0 commit comments

Comments
 (0)