xref: /netbsd-src/external/gpl3/gcc.old/dist/contrib/gcc-changelog/git_email.py (revision 4c3eb207d36f67d31994830c0a694161fc1ca39b)
1*4c3eb207Smrg#!/usr/bin/env python3
2*4c3eb207Smrg#
3*4c3eb207Smrg# This file is part of GCC.
4*4c3eb207Smrg#
5*4c3eb207Smrg# GCC is free software; you can redistribute it and/or modify it under
6*4c3eb207Smrg# the terms of the GNU General Public License as published by the Free
7*4c3eb207Smrg# Software Foundation; either version 3, or (at your option) any later
8*4c3eb207Smrg# version.
9*4c3eb207Smrg#
10*4c3eb207Smrg# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11*4c3eb207Smrg# WARRANTY; without even the implied warranty of MERCHANTABILITY or
12*4c3eb207Smrg# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13*4c3eb207Smrg# for more details.
14*4c3eb207Smrg#
15*4c3eb207Smrg# You should have received a copy of the GNU General Public License
16*4c3eb207Smrg# along with GCC; see the file COPYING3.  If not see
17*4c3eb207Smrg# <http://www.gnu.org/licenses/>.  */
18*4c3eb207Smrg
19*4c3eb207Smrgimport os
20*4c3eb207Smrgimport re
21*4c3eb207Smrgimport sys
22*4c3eb207Smrgfrom itertools import takewhile
23*4c3eb207Smrg
24*4c3eb207Smrgfrom dateutil.parser import parse
25*4c3eb207Smrg
26*4c3eb207Smrgfrom git_commit import GitCommit, GitInfo, decode_path
27*4c3eb207Smrg
28*4c3eb207Smrgfrom unidiff import PatchSet, PatchedFile
29*4c3eb207Smrg
30*4c3eb207SmrgDATE_PREFIX = 'Date: '
31*4c3eb207SmrgFROM_PREFIX = 'From: '
32*4c3eb207SmrgSUBJECT_PREFIX = 'Subject: '
33*4c3eb207Smrgsubject_patch_regex = re.compile(r'^\[PATCH( \d+/\d+)?\] ')
34*4c3eb207Smrgunidiff_supports_renaming = hasattr(PatchedFile(), 'is_rename')
35*4c3eb207Smrg
36*4c3eb207Smrg
37*4c3eb207Smrgclass GitEmail(GitCommit):
38*4c3eb207Smrg    def __init__(self, filename):
39*4c3eb207Smrg        self.filename = filename
40*4c3eb207Smrg        diff = PatchSet.from_filename(filename)
41*4c3eb207Smrg        date = None
42*4c3eb207Smrg        author = None
43*4c3eb207Smrg        subject = ''
44*4c3eb207Smrg
45*4c3eb207Smrg        subject_last = False
46*4c3eb207Smrg        with open(self.filename, 'r') as f:
47*4c3eb207Smrg            lines = f.read().splitlines()
48*4c3eb207Smrg        lines = list(takewhile(lambda line: line != '---', lines))
49*4c3eb207Smrg        for line in lines:
50*4c3eb207Smrg            if line.startswith(DATE_PREFIX):
51*4c3eb207Smrg                date = parse(line[len(DATE_PREFIX):])
52*4c3eb207Smrg            elif line.startswith(FROM_PREFIX):
53*4c3eb207Smrg                author = GitCommit.format_git_author(line[len(FROM_PREFIX):])
54*4c3eb207Smrg            elif line.startswith(SUBJECT_PREFIX):
55*4c3eb207Smrg                subject = line[len(SUBJECT_PREFIX):]
56*4c3eb207Smrg                subject_last = True
57*4c3eb207Smrg            elif subject_last and line.startswith(' '):
58*4c3eb207Smrg                subject += line
59*4c3eb207Smrg            elif line == '':
60*4c3eb207Smrg                break
61*4c3eb207Smrg            else:
62*4c3eb207Smrg                subject_last = False
63*4c3eb207Smrg
64*4c3eb207Smrg        if subject:
65*4c3eb207Smrg            subject = subject_patch_regex.sub('', subject)
66*4c3eb207Smrg        header = list(takewhile(lambda line: line != '', lines))
67*4c3eb207Smrg        # Note: commit message consists of email subject, empty line, email body
68*4c3eb207Smrg        message = [subject] + lines[len(header):]
69*4c3eb207Smrg
70*4c3eb207Smrg        modified_files = []
71*4c3eb207Smrg        for f in diff:
72*4c3eb207Smrg            # Strip "a/" and "b/" prefixes
73*4c3eb207Smrg            source = decode_path(f.source_file)[2:]
74*4c3eb207Smrg            target = decode_path(f.target_file)[2:]
75*4c3eb207Smrg
76*4c3eb207Smrg            if f.is_added_file:
77*4c3eb207Smrg                t = 'A'
78*4c3eb207Smrg            elif f.is_removed_file:
79*4c3eb207Smrg                t = 'D'
80*4c3eb207Smrg            elif unidiff_supports_renaming and f.is_rename:
81*4c3eb207Smrg                # Consider that renamed files are two operations: the deletion
82*4c3eb207Smrg                # of the original name and the addition of the new one.
83*4c3eb207Smrg                modified_files.append((source, 'D'))
84*4c3eb207Smrg                t = 'A'
85*4c3eb207Smrg            else:
86*4c3eb207Smrg                t = 'M'
87*4c3eb207Smrg            modified_files.append((target if t != 'D' else source, t))
88*4c3eb207Smrg        git_info = GitInfo(None, date, author, message, modified_files)
89*4c3eb207Smrg        super().__init__(git_info,
90*4c3eb207Smrg                         commit_to_info_hook=lambda x: None)
91*4c3eb207Smrg
92*4c3eb207Smrg
93*4c3eb207Smrgdef show_help():
94*4c3eb207Smrg    print("""usage: git_email.py [--help] [patch file ...]
95*4c3eb207Smrg
96*4c3eb207SmrgCheck git ChangeLog format of a patch
97*4c3eb207Smrg
98*4c3eb207SmrgWith zero arguments, process every patch file in the
99*4c3eb207Smrg./patches directory.
100*4c3eb207SmrgWith one argument, process the named patch file.
101*4c3eb207Smrg
102*4c3eb207SmrgPatch files must be in 'git format-patch' format.""")
103*4c3eb207Smrg    sys.exit(0)
104*4c3eb207Smrg
105*4c3eb207Smrg
106*4c3eb207Smrgif __name__ == '__main__':
107*4c3eb207Smrg    if len(sys.argv) == 2 and (sys.argv[1] == '-h' or sys.argv[1] == '--help'):
108*4c3eb207Smrg        show_help()
109*4c3eb207Smrg
110*4c3eb207Smrg    if len(sys.argv) == 1:
111*4c3eb207Smrg        allfiles = []
112*4c3eb207Smrg        for root, _dirs, files in os.walk('patches'):
113*4c3eb207Smrg            for f in files:
114*4c3eb207Smrg                full = os.path.join(root, f)
115*4c3eb207Smrg                allfiles.append(full)
116*4c3eb207Smrg
117*4c3eb207Smrg        success = 0
118*4c3eb207Smrg        for full in sorted(allfiles):
119*4c3eb207Smrg            email = GitEmail(full, False)
120*4c3eb207Smrg            print(email.filename)
121*4c3eb207Smrg            if email.success:
122*4c3eb207Smrg                success += 1
123*4c3eb207Smrg                print('  OK')
124*4c3eb207Smrg            else:
125*4c3eb207Smrg                for error in email.errors:
126*4c3eb207Smrg                    print('  ERR: %s' % error)
127*4c3eb207Smrg
128*4c3eb207Smrg        print()
129*4c3eb207Smrg        print('Successfully parsed: %d/%d' % (success, len(allfiles)))
130*4c3eb207Smrg    else:
131*4c3eb207Smrg        email = GitEmail(sys.argv[1])
132*4c3eb207Smrg        if email.success:
133*4c3eb207Smrg            print('OK')
134*4c3eb207Smrg            email.print_output()
135*4c3eb207Smrg        else:
136*4c3eb207Smrg            if not email.info.lines:
137*4c3eb207Smrg                print('Error: patch contains no parsed lines', file=sys.stderr)
138*4c3eb207Smrg            email.print_errors()
139*4c3eb207Smrg            sys.exit(1)
140