| #!/usr/bin/env python3 | |
| # | |
| # Copyright 2026, 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. | |
| # | |
| """Script that enforces Relnote: in the commit message has no issues.""" | |
| import argparse | |
| import re | |
| import sys | |
| def main(args=None): | |
| parser = argparse.ArgumentParser(prog='validateCommitMessage') | |
| parser.add_argument('--commit', required=True) | |
| args = parser.parse_args(args) | |
| if args.commit: | |
| relnote_pattern = re.compile( | |
| r'^Relnote:\s*(?:"""(.*?)"""|"(.*?)"|' | |
| r'((?:(?!^\s*$|^[a-zA-Z0-9-]+:).|\n)*))', | |
| re.IGNORECASE | re.MULTILINE | re.DOTALL | |
| ) | |
| all_case_case_words = [] | |
| suggested_replacements = [] | |
| for m in relnote_pattern.finditer(args.commit): | |
| full_match = m.group(0) | |
| match = next(g for g in m.groups() if g is not None) | |
| if m.groups()[0] is None and m.groups()[1] is None and '\n' in match.strip(): | |
| print( | |
| 'Error: Multi-line release notes must be surrounded by ' | |
| 'quotes (") or triple quotes (""").' | |
| ) | |
| sys.exit(1) | |
| # Remove all words surrounded by backticks | |
| text_without_backticks = re.sub(r'`[^`]*`', '', match) | |
| # Find CaseCase words: words containing at least one lowercase letter | |
| # followed by an uppercase letter | |
| pattern = r'\b[a-zA-Z0-9_]*[a-z][A-Z][a-zA-Z0-9_]*\b' | |
| case_case_words = re.findall(pattern, text_without_backticks) | |
| if case_case_words: | |
| all_case_case_words.extend(case_case_words) | |
| parts = full_match.split('`') | |
| for i in range(0, len(parts), 2): | |
| for word in set(case_case_words): | |
| parts[i] = re.sub(r'\b' + re.escape(word) + r'\b', f'`{word}`', parts[i]) | |
| suggested_replacements.append('`'.join(parts).strip()) | |
| else: | |
| suggested_replacements.append(full_match.strip()) | |
| if all_case_case_words: | |
| words = ', '.join(sorted(set(all_case_case_words))) | |
| replacements = '\n\n'.join(suggested_replacements) | |
| print( | |
| 'Error: The following words use CamelCase in the `Relnote:` ' | |
| 'tag in the commit message, but are not surrounded by ' | |
| f'backticks (`):\n{words}\n\n' | |
| f'Suggested replacement:\n{replacements}' | |
| ) | |
| sys.exit(1) | |
| sys.exit(0) | |
| if __name__ == '__main__': | |
| main() |