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, strict=False): 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, t)) 70 git_info = GitInfo(None, date, author, body, modified_files) 71 super().__init__(git_info, strict=strict, 72 commit_to_info_hook=lambda x: None) 73 74 75# With zero arguments, process every patch file in the ./patches directory. 76# With one argument, process the named patch file. 77# Patch files must be in 'git format-patch' format. 78if __name__ == '__main__': 79 if len(sys.argv) == 1: 80 allfiles = [] 81 for root, _dirs, files in os.walk('patches'): 82 for f in files: 83 full = os.path.join(root, f) 84 allfiles.append(full) 85 86 success = 0 87 for full in sorted(allfiles): 88 email = GitEmail(full, False) 89 print(email.filename) 90 if email.success: 91 success += 1 92 print(' OK') 93 else: 94 for error in email.errors: 95 print(' ERR: %s' % error) 96 97 print() 98 print('Successfully parsed: %d/%d' % (success, len(allfiles))) 99 else: 100 email = GitEmail(sys.argv[1], False) 101 if email.success: 102 print('OK') 103 email.print_output() 104 else: 105 if not email.info.lines: 106 print('Error: patch contains no parsed lines', file=sys.stderr) 107 email.print_errors() 108 sys.exit(1) 109