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