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