blob: 7e7efe5d0643bb248bb07ac4d958385a6357ba10 [file] [log] [blame]
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""Does presubmit checks for kernel technical debt.
This is intended to be used by `repo`. For more information, please see
https://chromium.googlesource.com/chromiumos/repohooks/+/refs/heads/main/README.md.
Enforces the following rules:
- If a patch is not from upstream, a UPSTREAM-TASK tag must be present
"""
import os
import re
import subprocess
import sys
TECH_DEBT_MSG = (
"This patch is not fully upstream. Please open a tracking bug here: "
"go/cros-kernel-technical-debt-bug and add a label UPSTREAM-TASK=b:XXXX "
"referencing it. A member of the review committee will review the CL. "
"Thank you."
)
TECH_DEBT_DUP_BUG_MSG = (
"UPSTREAM-TASK bugs cannot be the same as those present in the BUG tag."
)
def is_chromium_only(files):
"""Check if the CL modifies files that only belong to ChromiumOS"""
repo_path = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"], encoding="utf-8"
).strip()
for file in files:
file = os.path.relpath(file, repo_path)
if not file.startswith(
(
"chromeos/",
"OWNERS",
"PRESUBMIT.cfg",
"security/chromiumos/"
"unblocked_terms.txt"
)
):
return False
return True
def check_tech_debt(commit):
"""Inspect commit messages to validate tech_debt tags."""
commit_message = subprocess.check_output(
["git", "log", "-n", "1", "--format=%B", commit],
encoding="utf-8",
)
if commit_message.startswith(
(
"UPSTREAM:",
"FROMGIT:",
"WIP:",
"TEST:",
"TEST-ONLY:",
"BACKPORT:",
"Revert",
"Reland",
)
) and not commit_message.startswith("BACKPORT: FROMLIST"):
return True
bug_id = r"b:[0-9]{7,}"
bugs_ids = rf"({bug_id}(?:[, ]+{bug_id})*)"
bug_tag = rf"\nBUG={bugs_ids}+"
upstream_task_tag = rf"\nUPSTREAM-TASK={bugs_ids}+"
bug_line = re.findall(bug_tag, commit_message)
upstream_task_line = re.findall(upstream_task_tag, commit_message)
if upstream_task_line == []:
print(TECH_DEBT_MSG, file=sys.stderr)
return False
bugs = set()
for b in bug_line:
bugs |= set(re.findall(bug_id, b))
upstream_tasks = set()
for b in upstream_task_line:
upstream_tasks |= set(re.findall(bug_id, b))
if set(bugs) & set(upstream_tasks):
print(TECH_DEBT_DUP_BUG_MSG, file=sys.stderr)
return False
return True
def main():
"""Main function."""
presubmit_files = os.environ.get("PRESUBMIT_FILES")
if presubmit_files is None:
sys.exit("Need a value for PRESUBMIT_FILES")
presubmit_commit = os.environ.get("PRESUBMIT_COMMIT")
if presubmit_commit is None:
sys.exit("Need a value for PRESUBMIT_COMMIT")
if is_chromium_only(presubmit_files.splitlines()):
sys.exit(0)
if not check_tech_debt(presubmit_commit):
sys.exit(1)
if __name__ == "__main__":
main()