| #!/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() |