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