| #!/usr/bin/env python3 | |
| # Copyright (C) 2020-2026 Free Software Foundation, Inc. | |
| # | |
| # This file is part of GCC. | |
| # | |
| # GCC is free software; you can redistribute it and/or modify it under | |
| # the terms of the GNU General Public License as published by the Free | |
| # Software Foundation; either version 3, or (at your option) any later | |
| # version. | |
| # | |
| # GCC is distributed in the hope that it will be useful, but WITHOUT ANY | |
| # WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| # for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with GCC; see the file COPYING3. If not see | |
| # <http://www.gnu.org/licenses/>. | |
| import os | |
| import re | |
| import sys | |
| from itertools import takewhile | |
| from dateutil.parser import parse | |
| from git_commit import GitCommit, GitInfo, decode_path | |
| from unidiff import PatchSet, PatchedFile | |
| DATE_PREFIX = 'Date: ' | |
| FROM_PREFIX = 'From: ' | |
| SUBJECT_PREFIX = 'Subject: ' | |
| subject_patch_regex = re.compile(r'^\[PATCH( \d+/\d+)?\] ') | |
| unidiff_supports_renaming = hasattr(PatchedFile(), 'is_rename') | |
| class GitEmail(GitCommit): | |
| def __init__(self, filename): | |
| self.filename = filename | |
| date = None | |
| author = None | |
| subject = '' | |
| subject_last = False | |
| with open(self.filename, newline='\n') as f: | |
| data = f.read() | |
| diff = PatchSet(data) | |
| lines = data.splitlines() | |
| lines = list(takewhile(lambda line: line != '---', lines)) | |
| for line in lines: | |
| if line.startswith(DATE_PREFIX): | |
| date = parse(line[len(DATE_PREFIX):]) | |
| elif line.startswith(FROM_PREFIX): | |
| author = GitCommit.format_git_author(line[len(FROM_PREFIX):]) | |
| elif line.startswith(SUBJECT_PREFIX): | |
| subject = line[len(SUBJECT_PREFIX):] | |
| subject_last = True | |
| elif subject_last and line.startswith(' '): | |
| subject += line | |
| elif line == '': | |
| break | |
| else: | |
| subject_last = False | |
| if subject: | |
| subject = subject_patch_regex.sub('', subject) | |
| header = list(takewhile(lambda line: line != '', lines)) | |
| # Note: commit message consists of email subject, empty line, email body | |
| message = [subject] + lines[len(header):] | |
| modified_files = [] | |
| for f in diff: | |
| # Strip "a/" and "b/" prefixes | |
| source = decode_path(f.source_file)[2:] | |
| target = decode_path(f.target_file)[2:] | |
| if f.is_added_file: | |
| t = 'A' | |
| elif f.is_removed_file: | |
| t = 'D' | |
| elif unidiff_supports_renaming and f.is_rename: | |
| # Consider that renamed files are two operations: the deletion | |
| # of the original name and the addition of the new one. | |
| modified_files.append((source, 'D')) | |
| t = 'A' | |
| else: | |
| t = 'M' | |
| modified_files.append((target if t != 'D' else source, t)) | |
| git_info = GitInfo(None, date, author, message, modified_files) | |
| super().__init__(git_info, commit_to_info_hook=None) | |
| def show_help(): | |
| print("""usage: git_email.py [--help] [patch file ...] | |
| Check git ChangeLog format of a patch | |
| With zero arguments, process every patch file in the | |
| ./patches directory. | |
| With one argument, process the named patch file. | |
| Patch files must be in 'git format-patch' format.""") | |
| sys.exit(0) | |
| if __name__ == '__main__': | |
| if len(sys.argv) == 2 and (sys.argv[1] == '-h' or sys.argv[1] == '--help'): | |
| show_help() | |
| if len(sys.argv) == 1: | |
| allfiles = [] | |
| for root, _dirs, files in os.walk('patches'): | |
| for f in files: | |
| full = os.path.join(root, f) | |
| allfiles.append(full) | |
| success = 0 | |
| for full in sorted(allfiles): | |
| email = GitEmail(full) | |
| print(email.filename) | |
| if email.success: | |
| success += 1 | |
| print(' OK') | |
| for warning in email.warnings: | |
| print(' WARN: %s' % warning) | |
| else: | |
| for error in email.errors: | |
| print(' ERR: %s' % error) | |
| print() | |
| print('Successfully parsed: %d/%d' % (success, len(allfiles))) | |
| else: | |
| email = GitEmail(sys.argv[1]) | |
| if email.success: | |
| print('OK') | |
| email.print_output() | |
| email.print_warnings() | |
| else: | |
| if not email.info.lines: | |
| print('Error: patch contains no parsed lines', file=sys.stderr) | |
| email.print_errors() | |
| sys.exit(1) |