blob: eb73456659cf9a5251ae8a5a608df1fdf105d906 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2022 Google LLC. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# quick hacky script to check patches if they are candidates for lts. it checks
# only the non-merge commits.
import pkg_resources
import os
import git
import re
import sys
import argparse
from io import StringIO
pkg_resources.require("unidiff>=0.7.4")
from unidiff import PatchSet
global_debug = False
def debug_print(*args, **kwargs):
global global_var
if global_debug:
print(*args, **kwargs)
def contains_re(pf, tok):
for hnk in pf:
for ln in hnk:
if ln.is_context:
continue
# here means the line is either added or removed
txt = ln.value.strip()
if tok.search(txt) is not None:
return True
return False
def process_ps(ps):
score = 0
cpu_tok = re.compile(CPU_PATH_TOKEN)
doc_tok = re.compile(DOC_PATH_TOKEN)
for pf in ps:
if pf.is_binary_file or not pf.is_modified_file:
continue
if cpu_tok.search(pf.path) is not None:
debug_print("* change found in cpu path:", pf.path);
cpu_tok = re.compile(CPU_ERRATA_TOKEN)
if contains_re(pf, cpu_tok):
score = score + 1
debug_print(" found", CPU_ERRATA_TOKEN)
if doc_tok.search(pf.path) is not None:
debug_print("* change found in macros doc path:", pf.path);
doc_tok = re.compile(DOC_ERRATA_TOKEN)
if contains_re(pf, doc_tok):
score = score + 1
debug_print(" found", DOC_ERRATA_TOKEN)
return score
SUBJECT_TOKENS = r'fix\(cpus\)|revert\(cpus\)|fix\(errata\)|\(security\)'
CPU_PATH_TOKEN = r'lib/cpus/aarch(32|64)/.*\.S'
CPU_ERRATA_TOKEN = r'^report_errata ERRATA_'
DOC_PATH_TOKEN = r'docs/design/cpu-specific-build-macros.rst'
DOC_ERRATA_TOKEN = r'^^-\s*``ERRATA_'
# REBASE_DEPTH is number of commits from tip of integration branch that we need
# to check to find the commit that the current patch set is based on
REBASE_DEPTH = 50
# MAX_PATCHSET_DEPTH is the maximum number of patches that we expect in the current
# patch set. for each commit in the patch set we will look at past REBASE_DEPTH commits
# of integration branch. if there is a match we'd know the current patch set was based
# off of that matching commit. This is not necessarily the optimal method but I'm not
# familiar with gerrit API. If there is a way to do this better we should implement that.
MAX_PATCHSET_DEPTH = 50
CHECK_AGAINST = 'integration'
TO_CHECK = 'to_check'
## TODO: for case like 921081049ec3 where we need to refactor first for security
# patch to be applied then we should:
# 1. find the security patch
# 2. from that patch find CVE number if any
# 3. look for all patches that contain that CVE number in commit message
## TODO: similar to errata macros and rst file additions, we have CVE macros and rst file
# additions. so we can use similar logic for that.
## TODO: for security we should look for CVE numbed regex match and if found flag it
def main():
parser = argparse.ArgumentParser(prog="lts-triage.py", description="check patches for LTS candidacy")
parser.add_argument("--repo", required=True, help="path to tf-a git repo")
parser.add_argument("--email_path", required=True, help="path including the filename for email file")
parser.add_argument("--debug", help="print debug logs", action="store_true")
args = parser.parse_args()
global global_debug
global_debug = args.debug
file_str = "Below is an interesting patchset. Patches are listed in format {Subject}: {Score}.\n\n"
at_least_one_match = False
repo = git.Repo(args.repo)
# collect the integration hashes in a list
rebase_hashes = []
for cmt in repo.iter_commits(CHECK_AGAINST):
rebase_hashes.append(cmt.hexsha)
if len(rebase_hashes) == REBASE_DEPTH:
break
cnt = MAX_PATCHSET_DEPTH
for cmt in repo.iter_commits(TO_CHECK):
score = 0
# if we find a same commit hash among the ones we collected from integration branch
# then we have seen all the new patches in this patch set, so we should exit.
if cmt.hexsha in rebase_hashes:
print("## stopping because found sha1 common between the two branches: ", cmt.hexsha)
break;
# don't process merge commits
if len(cmt.parents) > 1:
continue
tok = re.compile(SUBJECT_TOKENS)
if tok.search(cmt.summary) is not None:
debug_print("## subject match")
score = score + 1
diff_text = repo.git.diff(cmt.hexsha + "~1", cmt.hexsha, ignore_blank_lines=True, ignore_space_at_eol=True)
ps = PatchSet(StringIO(diff_text))
debug_print("# score before process_ps:", score)
score = score + process_ps(ps)
debug_print("# score after process_ps:", score)
ln = f"{cmt.summary}: {score}"
print(ln)
if score > 0:
file_str += ln + "\n"
at_least_one_match = True
cnt = cnt - 1
if cnt == 0:
break
if at_least_one_match == True:
try:
with open(args.email_path, "x") as f:
f.write(file_str)
except:
print("\n\nERROR: Couldn't open email file due to error: ", sys.exc_info()[0])
if __name__ == '__main__':
main()