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