xref: /netbsd-src/external/gpl3/gcc/dist/contrib/gcc-changelog/git_update_version.py (revision 53b02e147d4ed531c0d2a5ca9b3e8026ba3e99b5)
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 argparse
20import datetime
21import os
22
23from git import Repo
24
25from git_repository import parse_git_revisions
26
27current_timestamp = datetime.datetime.now().strftime('%Y%m%d\n')
28
29
30def read_timestamp(path):
31    with open(path) as f:
32        return f.read()
33
34
35def prepend_to_changelog_files(repo, folder, git_commit, add_to_git):
36    if not git_commit.success:
37        for error in git_commit.errors:
38            print(error)
39        raise AssertionError()
40    for entry, output in git_commit.to_changelog_entries(use_commit_ts=True):
41        full_path = os.path.join(folder, entry, 'ChangeLog')
42        print('writing to %s' % full_path)
43        if os.path.exists(full_path):
44            with open(full_path) as f:
45                content = f.read()
46        else:
47            content = ''
48        with open(full_path, 'w+') as f:
49            f.write(output)
50            if content:
51                f.write('\n\n')
52                f.write(content)
53        if add_to_git:
54            repo.git.add(full_path)
55
56
57active_refs = ['master', 'releases/gcc-8', 'releases/gcc-9', 'releases/gcc-10']
58
59parser = argparse.ArgumentParser(description='Update DATESTAMP and generate '
60                                 'ChangeLog entries')
61parser.add_argument('-g', '--git-path', default='.',
62                    help='Path to git repository')
63parser.add_argument('-p', '--push', action='store_true',
64                    help='Push updated active branches')
65parser.add_argument('-d', '--dry-mode',
66                    help='Generate patch for ChangeLog entries and do it'
67                         ' even if DATESTAMP is unchanged; folder argument'
68                         ' is expected')
69parser.add_argument('-c', '--current', action='store_true',
70                    help='Modify current branch (--push argument is ignored)')
71args = parser.parse_args()
72
73repo = Repo(args.git_path)
74origin = repo.remotes['origin']
75
76
77def update_current_branch():
78    commit = repo.head.commit
79    commit_count = 1
80    while commit:
81        if (commit.author.email == 'gccadmin@gcc.gnu.org'
82                and commit.message.strip() == 'Daily bump.'):
83            break
84        # We support merge commits but only with 2 parensts
85        assert len(commit.parents) <= 2
86        commit = commit.parents[-1]
87        commit_count += 1
88
89    print('%d revisions since last Daily bump' % commit_count)
90    datestamp_path = os.path.join(args.git_path, 'gcc/DATESTAMP')
91    if (read_timestamp(datestamp_path) != current_timestamp
92            or args.dry_mode or args.current):
93        head = repo.head.commit
94        # if HEAD is a merge commit, start with second parent
95        # (branched that is being merged into the current one)
96        assert len(head.parents) <= 2
97        if len(head.parents) == 2:
98            head = head.parents[1]
99        commits = parse_git_revisions(args.git_path, '%s..%s'
100                                      % (commit.hexsha, head.hexsha))
101        for git_commit in reversed(commits):
102            prepend_to_changelog_files(repo, args.git_path, git_commit,
103                                       not args.dry_mode)
104        if args.dry_mode:
105            diff = repo.git.diff('HEAD')
106            patch = os.path.join(args.dry_mode,
107                                 branch.name.split('/')[-1] + '.patch')
108            with open(patch, 'w+') as f:
109                f.write(diff)
110            print('branch diff written to %s' % patch)
111            repo.git.checkout(force=True)
112        else:
113            # update timestamp
114            print('DATESTAMP will be changed:')
115            with open(datestamp_path, 'w+') as f:
116                f.write(current_timestamp)
117            repo.git.add(datestamp_path)
118            if not args.current:
119                repo.index.commit('Daily bump.')
120                if args.push:
121                    repo.git.push('origin', branch)
122                    print('branch is pushed')
123    else:
124        print('DATESTAMP unchanged')
125
126
127if args.current:
128    print('=== Working on the current branch ===', flush=True)
129    update_current_branch()
130else:
131    for ref in origin.refs:
132        assert ref.name.startswith('origin/')
133        name = ref.name[len('origin/'):]
134        if name in active_refs:
135            if name in repo.branches:
136                branch = repo.branches[name]
137            else:
138                branch = repo.create_head(name, ref).set_tracking_branch(ref)
139            print('=== Working on: %s ===' % branch, flush=True)
140            branch.checkout()
141            origin.pull(rebase=True)
142            print('branch pulled and checked out')
143            update_current_branch()
144            assert not repo.index.diff(None)
145            print('branch is done\n', flush=True)
146