177 lines
6.5 KiB
Python
Executable File
177 lines
6.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
#
|
|
# Copyright 2021, The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
"""Repohook script to run checks on TODOs in CHRE.
|
|
|
|
This script runs the following checks on TODOs in a commit:
|
|
|
|
1: Prints a warning if a TODO references the bug ID in the commit message.
|
|
This is mainly intended to minimize TODOs in the CHRE codebase, and to be
|
|
an active reminder to remove a TODO once a commit addresses the debt
|
|
mentioned.
|
|
|
|
2: Fails the repo upload if the current commit adds a TODO, but fails to
|
|
associate it with a bug-ID in the (usual) expected format of
|
|
'TODO(b/13371337).
|
|
|
|
A bug ID field in the commit message is REQUIRED for this script to work.
|
|
This can be ensured by adding a 'commit_msg_bug_field = true' hook to the
|
|
project's PREUPLOAD.cfg file. It is also recommended to add the
|
|
'ignore_merged_commits' option to avoid unexpected script behavior.
|
|
|
|
This script will work with any number of commits in the current repo
|
|
checkout.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
COMMIT_HASH = os.environ['PREUPLOAD_COMMIT']
|
|
|
|
# According to the repohooks documentation, only the warning and success IDs
|
|
# are mentioned - we use a random non-zero value (that's high enough to
|
|
# avoid confusion with errno values) as our error code.
|
|
REPO_ERROR_RETURN_CODE = 1337
|
|
REPO_WARNING_RETURN_CODE = 77
|
|
REPO_SUCCESS_RETURN_CODE = 0
|
|
|
|
def check_for_unassociated_todos() -> int:
|
|
"""Check if a TODO has a bug ID associated with it.
|
|
|
|
Check if a TODO has a bug ID, in the usual 'TODO(b/13371337): {desc}'
|
|
format. Also prints the line where said TODO was found.
|
|
|
|
Returns:
|
|
An error code if a TODO has no bugs associated with it.
|
|
"""
|
|
rc = REPO_SUCCESS_RETURN_CODE
|
|
commit_contents_cmd = 'git diff ' + COMMIT_HASH + '~ ' + COMMIT_HASH
|
|
diff_result_lines = subprocess.check_output(commit_contents_cmd,
|
|
shell=True,
|
|
encoding='UTF-8') \
|
|
.split('\n')
|
|
regex = r'TODO\(b\/([0-9]+)(?=[^\/]*$)'
|
|
|
|
for line in diff_result_lines:
|
|
if line.startswith('+') and not line.startswith('+++') and \
|
|
'TODO' in line and not re.findall(regex, line):
|
|
print('Found a TODO in the following line in the commit without an \
|
|
associated bug-ID!')
|
|
print(line)
|
|
print('Please include a bug ID in the format TODO(b/13371337)')
|
|
rc = REPO_ERROR_RETURN_CODE
|
|
|
|
return rc
|
|
|
|
def grep_for_todos(bug_id : str) -> int:
|
|
"""Searches for TODOs associated with the BUG ID referenced in the commit.
|
|
|
|
Args:
|
|
bug_id: Bug ID referenced in the commit.
|
|
|
|
Returns:
|
|
A warning code if current bug ID references any TODOs.
|
|
"""
|
|
grep_result = None
|
|
rc = REPO_SUCCESS_RETURN_CODE
|
|
git_repo_path_cmd = 'git rev-parse --show-toplevel'
|
|
repo_path = ' ' + subprocess.check_output(git_repo_path_cmd, shell=True,
|
|
encoding='UTF-8')
|
|
|
|
grep_base_cmd = 'grep -nri '
|
|
grep_file_filters = '--include \*.h --include \*.cc --include \*.cpp --include \*.c '
|
|
grep_shell_cmd = grep_base_cmd + grep_file_filters + bug_id + repo_path
|
|
try:
|
|
grep_result = subprocess.check_output(grep_shell_cmd, shell=True,
|
|
encoding='UTF-8')
|
|
except subprocess.CalledProcessError as e:
|
|
if e.returncode != 1:
|
|
# A return code of 1 means that grep returned a 'NOT_FOUND', which is
|
|
# our ideal scenario! A return code of > 1 means something went very
|
|
# wrong with grep. We still return a success here, since there's
|
|
# nothing much else we can do (and this tool is intended to be mostly
|
|
# informational).
|
|
print('ERROR: grep failed with err code {}'.format(e.returncode),
|
|
file=sys.stderr)
|
|
print('The grep command that was run was:\n{}'.format(grep_shell_cmd),
|
|
file=sys.stderr)
|
|
|
|
if grep_result is not None:
|
|
print('Matching TODOs found for the Bug-ID in the commit message..')
|
|
print('Hash of the current commit being checked: {}'
|
|
.format(COMMIT_HASH))
|
|
grep_result = grep_result.replace(repo_path + '/', '')
|
|
print(grep_result)
|
|
rc = REPO_WARNING_RETURN_CODE
|
|
|
|
return rc
|
|
|
|
def get_bug_id_for_current_commit() -> str:
|
|
"""Get the Bug ID for the current commit
|
|
|
|
Returns:
|
|
The bug ID for the current commit.
|
|
"""
|
|
git_current_commit_msg_cmd = 'git log --format=%B -n 1 '
|
|
commit_msg_lines_cmd = git_current_commit_msg_cmd + COMMIT_HASH
|
|
commit_msg_lines_list = subprocess.check_output(commit_msg_lines_cmd,
|
|
shell=True,
|
|
encoding='UTF-8') \
|
|
.split('\n')
|
|
try:
|
|
bug_id_line = \
|
|
[line for line in commit_msg_lines_list if \
|
|
any(word in line.lower() for word in ['bug:', 'fixes:'])][0]
|
|
except IndexError:
|
|
print('Please include a Bug or Fixes field in the commit message')
|
|
sys.exit(-1);
|
|
return bug_id_line.split(':')[1].strip()
|
|
|
|
def is_file_in_diff(filename : str) -> bool:
|
|
"""Check if a given filename is part of the commit.
|
|
|
|
Args:
|
|
filename: filename to check in the git diff.
|
|
|
|
Returns:
|
|
True if the file is part of the commit.
|
|
"""
|
|
commit_contents_cmd = 'git diff ' + COMMIT_HASH + '~ ' + COMMIT_HASH
|
|
diff_result = subprocess.check_output(commit_contents_cmd, shell=True,
|
|
encoding='UTF-8')
|
|
return filename in diff_result
|
|
|
|
def main():
|
|
# This script has a bunch of TODOs peppered around, though not with the
|
|
# same intention as the checks that are being performed. Skip the checks
|
|
# if we're committing changes to this script! One caveat is that we
|
|
# should avoid pushing in changes to other code if we're committing
|
|
# changes to this script.
|
|
rc = REPO_SUCCESS_RETURN_CODE
|
|
if not is_file_in_diff(os.path.basename(__file__)):
|
|
bug_id = get_bug_id_for_current_commit()
|
|
grep_rc = grep_for_todos(bug_id)
|
|
check_rc = check_for_unassociated_todos()
|
|
rc = max(grep_rc, check_rc)
|
|
sys.exit(rc)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|