1#!/usr/bin/env python3 2# 3# This file is part of GCC. 4# 5# GCC is free software; you can redistribute it and/or modify it under 6# the terms of the GNU General Public License as published by the Free 7# Software Foundation; either version 3, or (at your option) any later 8# version. 9# 10# GCC is distributed in the hope that it will be useful, but WITHOUT ANY 11# WARRANTY; without even the implied warranty of MERCHANTABILITY or 12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13# for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with GCC; see the file COPYING3. If not see 17# <http://www.gnu.org/licenses/>. */ 18 19import os 20import re 21import sys 22from itertools import takewhile 23 24from dateutil.parser import parse 25 26from git_commit import GitCommit, GitInfo, decode_path 27 28from unidiff import PatchSet, PatchedFile 29 30DATE_PREFIX = 'Date: ' 31FROM_PREFIX = 'From: ' 32SUBJECT_PREFIX = 'Subject: ' 33subject_patch_regex = re.compile(r'^\[PATCH( \d+/\d+)?\] ') 34unidiff_supports_renaming = hasattr(PatchedFile(), 'is_rename') 35 36 37class GitEmail(GitCommit): 38 def __init__(self, filename): 39 self.filename = filename 40 diff = PatchSet.from_filename(filename) 41 date = None 42 author = None 43 subject = '' 44 45 subject_last = False 46 with open(self.filename, 'r') as f: 47 lines = f.read().splitlines() 48 lines = list(takewhile(lambda line: line != '---', lines)) 49 for line in lines: 50 if line.startswith(DATE_PREFIX): 51 date = parse(line[len(DATE_PREFIX):]) 52 elif line.startswith(FROM_PREFIX): 53 author = GitCommit.format_git_author(line[len(FROM_PREFIX):]) 54 elif line.startswith(SUBJECT_PREFIX): 55 subject = line[len(SUBJECT_PREFIX):] 56 subject_last = True 57 elif subject_last and line.startswith(' '): 58 subject += line 59 elif line == '': 60 break 61 else: 62 subject_last = False 63 64 if subject: 65 subject = subject_patch_regex.sub('', subject) 66 header = list(takewhile(lambda line: line != '', lines)) 67 # Note: commit message consists of email subject, empty line, email body 68 message = [subject] + lines[len(header):] 69 70 modified_files = [] 71 for f in diff: 72 # Strip "a/" and "b/" prefixes 73 source = decode_path(f.source_file)[2:] 74 target = decode_path(f.target_file)[2:] 75 76 if f.is_added_file: 77 t = 'A' 78 elif f.is_removed_file: 79 t = 'D' 80 elif unidiff_supports_renaming and f.is_rename: 81 # Consider that renamed files are two operations: the deletion 82 # of the original name and the addition of the new one. 83 modified_files.append((source, 'D')) 84 t = 'A' 85 else: 86 t = 'M' 87 modified_files.append((target if t != 'D' else source, t)) 88 git_info = GitInfo(None, date, author, message, modified_files) 89 super().__init__(git_info, 90 commit_to_info_hook=lambda x: None) 91 92 93def show_help(): 94 print("""usage: git_email.py [--help] [patch file ...] 95 96Check git ChangeLog format of a patch 97 98With zero arguments, process every patch file in the 99./patches directory. 100With one argument, process the named patch file. 101 102Patch files must be in 'git format-patch' format.""") 103 sys.exit(0) 104 105 106if __name__ == '__main__': 107 if len(sys.argv) == 2 and (sys.argv[1] == '-h' or sys.argv[1] == '--help'): 108 show_help() 109 110 if len(sys.argv) == 1: 111 allfiles = [] 112 for root, _dirs, files in os.walk('patches'): 113 for f in files: 114 full = os.path.join(root, f) 115 allfiles.append(full) 116 117 success = 0 118 for full in sorted(allfiles): 119 email = GitEmail(full, False) 120 print(email.filename) 121 if email.success: 122 success += 1 123 print(' OK') 124 else: 125 for error in email.errors: 126 print(' ERR: %s' % error) 127 128 print() 129 print('Successfully parsed: %d/%d' % (success, len(allfiles))) 130 else: 131 email = GitEmail(sys.argv[1]) 132 if email.success: 133 print('OK') 134 email.print_output() 135 else: 136 if not email.info.lines: 137 print('Error: patch contains no parsed lines', file=sys.stderr) 138 email.print_errors() 139 sys.exit(1) 140