#! /usr/bin/env python3 import sys import re import argparse # partially copied from tools/repohooks/rh/hooks.py TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the following case-sensitive regex: %s The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags. As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag. For legacy flags use EXEMPT with your flag name. Some examples below: Flag: NONE Repohook Update Flag: TEST_ONLY Flag: EXEMPT resource only update Flag: EXEMPT bugfix Flag: EXEMPT refactor Flag: com.android.launcher3.enable_twoline_allapps Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats. """ def main(): """Check the commit message for a 'Flag:' line.""" parser = argparse.ArgumentParser( description='Check the commit message for a Flag: line.') parser.add_argument('--msg', metavar='msg', type=str, nargs='?', default='HEAD', help='commit message to process.') parser.add_argument( '--files', metavar='files', nargs='?', default='', help= 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.') parser.add_argument( '--project', metavar='project', type=str, nargs='?', default='', help= 'REPO_PROJECT in repo upload to determine whether the check should run for this project.') # Parse the arguments args = parser.parse_args() desc = args.msg files = args.files project = args.project if not should_run_path(project, files): return field = 'Flag' none = 'NONE' testOnly = 'TEST_ONLY' docsOnly = 'DOCS_ONLY' exempt = 'EXEMPT' justification = '' # Aconfig Flag name format = . # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3 # For now alphabets, digits, "_", "." characters are allowed in flag name. # Checks if there is "one dot" between packageName and flagName and not adding stricter format check #common_typos_disable flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)' # None and Exempt needs justification exemptRegex = fr'{exempt}\s*[a-zA-Z]+' noneRegex = fr'{none}\s*[a-zA-Z]+' #common_typos_enable readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: .\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly flagRegex = fr'^{field}: .*$' check_flag = re.compile(flagRegex) #Flag: # Ignore case for flag name format. flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*' check_flagName = re.compile(flagNameRegex) #Flag: flagError = False foundFlag = [] # Check for multiple "Flag:" lines and all lines should match this format for line in desc.splitlines(): if check_flag.match(line): if not check_flagName.match(line): flagError = True break foundFlag.append(line) # Throw error if # 1. No "Flag:" line is found # 2. "Flag:" doesn't follow right format. if (not foundFlag) or (flagError): error = TEST_MSG % (readableRegexMsg) print(error) sys.exit(1) sys.exit(0) def should_run_path(project, files): """Returns a boolean if this check should run with these paths. If you want to check for a particular subdirectory under the path, add a check here, call should_run_files and check for a specific sub dir path in should_run_files. """ if not project: return False if project == 'platform/frameworks/base': return should_run_files(files) # Default case, run for all other projects which calls this script. return True def should_run_files(files): """Returns a boolean if this check should run with these files.""" if not files: return False if 'packages/SystemUI' in files: return True return False if __name__ == '__main__': main()