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 sys 21from itertools import takewhile 22 23from dateutil.parser import parse 24 25from git_commit import GitCommit, GitInfo, decode_path 26 27from unidiff import PatchSet, PatchedFile 28 29DATE_PREFIX = 'Date: ' 30FROM_PREFIX = 'From: ' 31unidiff_supports_renaming = hasattr(PatchedFile(), 'is_rename') 32 33 34class GitEmail(GitCommit): 35 def __init__(self, filename): 36 self.filename = filename 37 diff = PatchSet.from_filename(filename) 38 date = None 39 author = None 40 41 with open(self.filename, 'r') as f: 42 lines = f.read().splitlines() 43 lines = list(takewhile(lambda line: line != '---', lines)) 44 for line in lines: 45 if line.startswith(DATE_PREFIX): 46 date = parse(line[len(DATE_PREFIX):]) 47 elif line.startswith(FROM_PREFIX): 48 author = GitCommit.format_git_author(line[len(FROM_PREFIX):]) 49 header = list(takewhile(lambda line: line != '', lines)) 50 body = lines[len(header) + 1:] 51 52 modified_files = [] 53 for f in diff: 54 # Strip "a/" and "b/" prefixes 55 source = decode_path(f.source_file)[2:] 56 target = decode_path(f.target_file)[2:] 57 58 if f.is_added_file: 59 t = 'A' 60 elif f.is_removed_file: 61 t = 'D' 62 elif unidiff_supports_renaming and f.is_rename: 63 # Consider that renamed files are two operations: the deletion 64 # of the original name and the addition of the new one. 65 modified_files.append((source, 'D')) 66 t = 'A' 67 else: 68 t = 'M' 69 modified_files.append((target if t != 'D' else source, t)) 70 git_info = GitInfo(None, date, author, body, modified_files) 71 super().__init__(git_info, 72 commit_to_info_hook=lambda x: None) 73 74 75def show_help(): 76 print("""usage: git_email.py [--help] [patch file ...] 77 78Check git ChangeLog format of a patch 79 80With zero arguments, process every patch file in the 81./patches directory. 82With one argument, process the named patch file. 83 84Patch files must be in 'git format-patch' format.""") 85 sys.exit(0) 86 87 88if __name__ == '__main__': 89 if len(sys.argv) == 2 and (sys.argv[1] == '-h' or sys.argv[1] == '--help'): 90 show_help() 91 92 if len(sys.argv) == 1: 93 allfiles = [] 94 for root, _dirs, files in os.walk('patches'): 95 for f in files: 96 full = os.path.join(root, f) 97 allfiles.append(full) 98 99 success = 0 100 for full in sorted(allfiles): 101 email = GitEmail(full, False) 102 print(email.filename) 103 if email.success: 104 success += 1 105 print(' OK') 106 else: 107 for error in email.errors: 108 print(' ERR: %s' % error) 109 110 print() 111 print('Successfully parsed: %d/%d' % (success, len(allfiles))) 112 else: 113 email = GitEmail(sys.argv[1]) 114 if email.success: 115 print('OK') 116 email.print_output() 117 else: 118 if not email.info.lines: 119 print('Error: patch contains no parsed lines', file=sys.stderr) 120 email.print_errors() 121 sys.exit(1) 122