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