xref: /netbsd-src/external/gpl3/gcc/dist/contrib/gcc-changelog/git_email.py (revision a448f87c3717b704e85b1f0e5d631e4b5a0aae17)
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