Skip to content

Commit 4099955

Browse files
committed
ci_scripts: check_patch.py: add
Migrate check_patch.py from the wheel_builder repository, making no changes. Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
1 parent a0446f0 commit 4099955

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

ci_scripts/check_patch.py

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

0 commit comments

Comments
 (0)