blob: d182c94114db6d8c1ac64930846bab5598154ade [file] [log] [blame]
Arthur Shed8cd8db2024-03-11 09:54:24 -07001#!/usr/bin/env python3
2#
3# Copyright (c) 2022 Google LLC. All rights reserved.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6
7# quick hacky script to check patches if they are candidates for lts. it checks
8# only the non-merge commits.
9
10import os
11import git
12import re
13import sys
14import csv
15import argparse
16import json
17import subprocess
Arthur Shef41fb062025-08-11 16:25:50 -070018from datetime import datetime
Arthur Shed8cd8db2024-03-11 09:54:24 -070019from io import StringIO
20from unidiff import PatchSet
21from config import MESSAGE_TOKENS, CPU_PATH_TOKEN, CPU_ERRATA_TOKEN, DOC_PATH_TOKEN, DOC_ERRATA_TOKEN
22
23global_debug = False
24def debug_print(*args, **kwargs):
25 global global_var
26 if global_debug:
27 print(*args, **kwargs)
28
29def contains_re(pf, tok):
30 for hnk in pf:
31 for ln in hnk:
32 if ln.is_context:
33 continue
34 # here means the line is either added or removed
35 txt = ln.value.strip()
36 if tok.search(txt) is not None:
37 return True
38
39 return False
40
41def process_ps(ps):
42 score = 0
43
44 cpu_tok = re.compile(CPU_PATH_TOKEN)
45 doc_tok = re.compile(DOC_PATH_TOKEN)
46
47 for pf in ps:
48 if pf.is_binary_file or not pf.is_modified_file:
49 continue
50 if cpu_tok.search(pf.path) is not None:
51 debug_print("* change found in cpu path:", pf.path);
52 cpu_tok = re.compile(CPU_ERRATA_TOKEN)
53 if contains_re(pf, cpu_tok):
54 score = score + 1
55 debug_print(" found", CPU_ERRATA_TOKEN)
56
57 if doc_tok.search(pf.path) is not None:
58 debug_print("* change found in macros doc path:", pf.path);
59 doc_tok = re.compile(DOC_ERRATA_TOKEN)
60 if contains_re(pf, doc_tok):
61 score = score + 1
62 debug_print(" found", DOC_ERRATA_TOKEN)
63
64 return score
65
66def query_gerrit(gerrit_user, ssh_key_path, change_id):
67 ssh_command = [
68 "ssh",
69 "-o", "UserKnownHostsFile=/dev/null",
70 "-o", "StrictHostKeyChecking=no",
71 "-o", "PubkeyAcceptedKeyTypes=+ssh-rsa",
72 "-p", "29418",
73 "-i", ssh_key_path,
74 f"{gerrit_user}@review.trustedfirmware.org",
75 f"gerrit query --format=JSON change:'{change_id}'",
76 "repo:'TF-A/trusted-firmware-a'"
77 ]
78
79 try:
80 result = subprocess.run(ssh_command, capture_output=True, text=True, check=True)
81 output = result.stdout.strip().split("\n")
82 changes = [json.loads(line) for line in output if line.strip()]
83 # Create a dictionary with branch as key and URL as value
84 branches_urls = {change["branch"]: change["url"] for change in changes if "branch" in change and "url" in change}
85 return branches_urls
86
87 except subprocess.CalledProcessError as e:
88 print("Error executing SSH command:", e)
89 return {}
90
91# REBASE_DEPTH is number of commits from tip of the LTS branch that we need
92# to check to find the commit that the current patch set is based on
93REBASE_DEPTH = 20
94
95
96## TODO: for case like 921081049ec3 where we need to refactor first for security
97# patch to be applied then we should:
98# 1. find the security patch
99# 2. from that patch find CVE number if any
100# 3. look for all patches that contain that CVE number in commit message
101
102## TODO: similar to errata macros and rst file additions, we have CVE macros and rst file
103# additions. so we can use similar logic for that.
104
105## TODO: for security we should look for CVE numbed regex match and if found flag it
106def main():
Arthur She63091622024-04-12 06:37:35 -0700107 at_least_one_match = False
Arthur Shed8cd8db2024-03-11 09:54:24 -0700108 parser = argparse.ArgumentParser(prog="lts-triage.py", description="check patches for LTS candidacy")
109 parser.add_argument("--repo", required=True, help="path to tf-a git repo")
110 parser.add_argument("--csv_path", required=True, help="path including the filename for CSV file")
111 parser.add_argument("--lts", required=True, help="LTS branch, ex. lts-v2.8")
112 parser.add_argument("--gerrit_user", required=True, help="The user id to perform the Gerrit query")
113 parser.add_argument("--ssh_keyfile", required=True, help="The SSH keyfile")
114 parser.add_argument("--debug", help="print debug logs", action="store_true")
115
116 args = parser.parse_args()
117 lts_branch = args.lts
118 gerrit_user = args.gerrit_user
119 ssh_keyfile = args.ssh_keyfile
120 global global_debug
121 global_debug = args.debug
122
Arthur Shef41fb062025-08-11 16:25:50 -0700123 csv_columns = ["index", "commit id in the integration branch", "committer date", "commit summary",
Arthur Shed8cd8db2024-03-11 09:54:24 -0700124 "score", "Gerrit Change-Id", "patch link for the LTS branch",
Arthur Shef41fb062025-08-11 16:25:50 -0700125 "patch link for the integration branch", "To be cherry-picked"]
Arthur Shed8cd8db2024-03-11 09:54:24 -0700126 csv_data = []
Arthur Shed8cd8db2024-03-11 09:54:24 -0700127
128 repo = git.Repo(args.repo)
129
130 # collect the LTS hashes in a list
131 lts_change_ids = set() # Set to store Gerrit Change-Ids from the LTS branch
132
133 for cmt in repo.iter_commits(lts_branch):
134 # Extract Gerrit Change-Id from the commit message
135 change_id_match = re.search(r'Change-Id:\s*(\w+)', cmt.message)
136 if change_id_match:
137 lts_change_ids.add(change_id_match.group(1))
138
139 if len(lts_change_ids) >= REBASE_DEPTH:
140 break
141
142 for cmt in repo.iter_commits('integration'):
143 score = 0
144
145 # if we find a same Change-Id among the ones we collected from the LTS branch
146 # then we have seen all the new patches in the integration branch, so we should exit.
147 change_id_match = re.search(r'Change-Id:\s*(\w+)', cmt.message)
148 if change_id_match:
149 change_id = change_id_match.group(1)
150 if change_id in lts_change_ids:
151 print("## stopping because found common Gerrit Change-Id between the two branches: ", change_id)
152 break;
153
154 # don't process merge commits
155 if len(cmt.parents) > 1:
156 continue
157
158 tok = re.compile(MESSAGE_TOKENS, re.IGNORECASE)
159 if tok.search(cmt.message) is not None:
160 debug_print("## commit message match")
161 score = score + 1
162
163 diff_text = repo.git.diff(cmt.hexsha + "~1", cmt.hexsha, ignore_blank_lines=True, ignore_space_at_eol=True)
164 ps = PatchSet(StringIO(diff_text))
165 debug_print("# score before process_ps:", score)
166 score = score + process_ps(ps)
167 debug_print("# score after process_ps:", score)
168
169 ln = f"{cmt.summary}: {score}"
170 print(ln)
171
172 if score > 0:
173 gerrit_links = query_gerrit(gerrit_user, ssh_keyfile, change_id)
174 # Append data to CSV
175 csv_data.append({
Arthur Shed8cd8db2024-03-11 09:54:24 -0700176 "commit id in the integration branch": cmt.hexsha,
Arthur Shef41fb062025-08-11 16:25:50 -0700177 "committer date": cmt.committed_date,
Arthur Shed8cd8db2024-03-11 09:54:24 -0700178 "commit summary": cmt.summary,
179 "score": score,
180 "Gerrit Change-Id": change_id,
181 "patch link for the LTS branch": gerrit_links.get(lts_branch, "N/A"),
Arthur Shef41fb062025-08-11 16:25:50 -0700182 "patch link for the integration branch": gerrit_links.get("integration", "N/A"),
183 "To be cherry-picked": "N" if gerrit_links.get(lts_branch) else "Y"
Arthur Shed8cd8db2024-03-11 09:54:24 -0700184 })
Arthur Shed8cd8db2024-03-11 09:54:24 -0700185 at_least_one_match = True
186
187 if at_least_one_match == True:
188 try:
Arthur Shef41fb062025-08-11 16:25:50 -0700189 # Sort by committer date first (from oldest to newest)
190 csv_data.sort(key=lambda row: int(row["committer date"]))
191
192 idx = 1
Arthur Shed8cd8db2024-03-11 09:54:24 -0700193 with open(args.csv_path, "w", newline='') as csvfile:
194 writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
195 writer.writeheader()
196 for data in csv_data:
Arthur Shef41fb062025-08-11 16:25:50 -0700197 # Convert timestamp to human-readable date before writing
198 ts = int(data["committer date"])
199 data["index"] = idx
200 data["committer date"] = datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
Arthur Shed8cd8db2024-03-11 09:54:24 -0700201 writer.writerow(data)
Arthur Shef41fb062025-08-11 16:25:50 -0700202 idx += 1
Arthur Shed8cd8db2024-03-11 09:54:24 -0700203 except:
204 print("\n\nERROR: Couldn't open CSV file due to error: ", sys.exc_info()[0])
205
206if __name__ == '__main__':
207 main()