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