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