1fb8a8121Smrg#!/usr/bin/env python3 2fb8a8121Smrg# 3fb8a8121Smrg# This file is part of GCC. 4fb8a8121Smrg# 5fb8a8121Smrg# GCC is free software; you can redistribute it and/or modify it under 6fb8a8121Smrg# the terms of the GNU General Public License as published by the Free 7fb8a8121Smrg# Software Foundation; either version 3, or (at your option) any later 8fb8a8121Smrg# version. 9fb8a8121Smrg# 10fb8a8121Smrg# GCC is distributed in the hope that it will be useful, but WITHOUT ANY 11fb8a8121Smrg# WARRANTY; without even the implied warranty of MERCHANTABILITY or 12fb8a8121Smrg# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13fb8a8121Smrg# for more details. 14fb8a8121Smrg# 15fb8a8121Smrg# You should have received a copy of the GNU General Public License 16fb8a8121Smrg# along with GCC; see the file COPYING3. If not see 17fb8a8121Smrg# <http://www.gnu.org/licenses/>. */ 18fb8a8121Smrg 19fb8a8121Smrgimport argparse 20fb8a8121Smrgimport datetime 21fb8a8121Smrgimport os 22fb8a8121Smrg 23fb8a8121Smrgfrom git import Repo 24fb8a8121Smrg 25fb8a8121Smrgfrom git_repository import parse_git_revisions 26fb8a8121Smrg 27fb8a8121Smrgcurrent_timestamp = datetime.datetime.now().strftime('%Y%m%d\n') 28fb8a8121Smrg 29a448f87cSmrg# Skip the following commits, they cannot be correctly processed 30a448f87cSmrgIGNORED_COMMITS = ( 31a448f87cSmrg 'c2be82058fb40f3ae891c68d185ff53e07f14f45', 32a448f87cSmrg '04a040d907a83af54e0a98bdba5bfabc0ef4f700', 33*b1e83836Smrg '2e96b5f14e4025691b57d2301d71aa6092ed44bc', 34*b1e83836Smrg '3ab5c8cd03d92bf4ec41e351820349d92fbc40c4', 35*b1e83836Smrg '86d8e0c0652ef5236a460b75c25e4f7093cc0651') 36a448f87cSmrg 37fb8a8121Smrg 38fb8a8121Smrgdef read_timestamp(path): 39fb8a8121Smrg with open(path) as f: 40fb8a8121Smrg return f.read() 41fb8a8121Smrg 42fb8a8121Smrg 43fb8a8121Smrgdef prepend_to_changelog_files(repo, folder, git_commit, add_to_git): 44fb8a8121Smrg if not git_commit.success: 45fb8a8121Smrg for error in git_commit.errors: 46fb8a8121Smrg print(error) 47fb8a8121Smrg raise AssertionError() 48fb8a8121Smrg for entry, output in git_commit.to_changelog_entries(use_commit_ts=True): 49fb8a8121Smrg full_path = os.path.join(folder, entry, 'ChangeLog') 50fb8a8121Smrg print('writing to %s' % full_path) 51fb8a8121Smrg if os.path.exists(full_path): 52fb8a8121Smrg with open(full_path) as f: 53fb8a8121Smrg content = f.read() 54fb8a8121Smrg else: 55fb8a8121Smrg content = '' 56fb8a8121Smrg with open(full_path, 'w+') as f: 57fb8a8121Smrg f.write(output) 58fb8a8121Smrg if content: 59fb8a8121Smrg f.write('\n\n') 60fb8a8121Smrg f.write(content) 61fb8a8121Smrg if add_to_git: 62fb8a8121Smrg repo.git.add(full_path) 63fb8a8121Smrg 64fb8a8121Smrg 65a448f87cSmrgactive_refs = ['master', 'releases/gcc-9', 'releases/gcc-10', 66a448f87cSmrg 'releases/gcc-11'] 67fb8a8121Smrg 68fb8a8121Smrgparser = argparse.ArgumentParser(description='Update DATESTAMP and generate ' 69fb8a8121Smrg 'ChangeLog entries') 70fb8a8121Smrgparser.add_argument('-g', '--git-path', default='.', 71fb8a8121Smrg help='Path to git repository') 72fb8a8121Smrgparser.add_argument('-p', '--push', action='store_true', 73fb8a8121Smrg help='Push updated active branches') 74fb8a8121Smrgparser.add_argument('-d', '--dry-mode', 75fb8a8121Smrg help='Generate patch for ChangeLog entries and do it' 76fb8a8121Smrg ' even if DATESTAMP is unchanged; folder argument' 77fb8a8121Smrg ' is expected') 78fb8a8121Smrgparser.add_argument('-c', '--current', action='store_true', 79fb8a8121Smrg help='Modify current branch (--push argument is ignored)') 80fb8a8121Smrgargs = parser.parse_args() 81fb8a8121Smrg 82fb8a8121Smrgrepo = Repo(args.git_path) 83fb8a8121Smrgorigin = repo.remotes['origin'] 84fb8a8121Smrg 85fb8a8121Smrg 86a448f87cSmrgdef update_current_branch(ref_name): 87fb8a8121Smrg commit = repo.head.commit 88fb8a8121Smrg commit_count = 1 89fb8a8121Smrg while commit: 90fb8a8121Smrg if (commit.author.email == 'gccadmin@gcc.gnu.org' 91fb8a8121Smrg and commit.message.strip() == 'Daily bump.'): 92fb8a8121Smrg break 93fb8a8121Smrg # We support merge commits but only with 2 parensts 94fb8a8121Smrg assert len(commit.parents) <= 2 95fb8a8121Smrg commit = commit.parents[-1] 96fb8a8121Smrg commit_count += 1 97fb8a8121Smrg 98fb8a8121Smrg print('%d revisions since last Daily bump' % commit_count) 99fb8a8121Smrg datestamp_path = os.path.join(args.git_path, 'gcc/DATESTAMP') 100fb8a8121Smrg if (read_timestamp(datestamp_path) != current_timestamp 101fb8a8121Smrg or args.dry_mode or args.current): 102fb8a8121Smrg head = repo.head.commit 103fb8a8121Smrg # if HEAD is a merge commit, start with second parent 104fb8a8121Smrg # (branched that is being merged into the current one) 105fb8a8121Smrg assert len(head.parents) <= 2 106fb8a8121Smrg if len(head.parents) == 2: 107fb8a8121Smrg head = head.parents[1] 108fb8a8121Smrg commits = parse_git_revisions(args.git_path, '%s..%s' 109a448f87cSmrg % (commit.hexsha, head.hexsha), ref_name) 110a448f87cSmrg commits = [c for c in commits if c.info.hexsha not in IGNORED_COMMITS] 111fb8a8121Smrg for git_commit in reversed(commits): 112fb8a8121Smrg prepend_to_changelog_files(repo, args.git_path, git_commit, 113fb8a8121Smrg not args.dry_mode) 114fb8a8121Smrg if args.dry_mode: 115fb8a8121Smrg diff = repo.git.diff('HEAD') 116fb8a8121Smrg patch = os.path.join(args.dry_mode, 117fb8a8121Smrg branch.name.split('/')[-1] + '.patch') 118fb8a8121Smrg with open(patch, 'w+') as f: 119fb8a8121Smrg f.write(diff) 120fb8a8121Smrg print('branch diff written to %s' % patch) 121fb8a8121Smrg repo.git.checkout(force=True) 122fb8a8121Smrg else: 123fb8a8121Smrg # update timestamp 124fb8a8121Smrg print('DATESTAMP will be changed:') 125fb8a8121Smrg with open(datestamp_path, 'w+') as f: 126fb8a8121Smrg f.write(current_timestamp) 127fb8a8121Smrg repo.git.add(datestamp_path) 128fb8a8121Smrg if not args.current: 129fb8a8121Smrg repo.index.commit('Daily bump.') 130fb8a8121Smrg if args.push: 131fb8a8121Smrg repo.git.push('origin', branch) 132fb8a8121Smrg print('branch is pushed') 133fb8a8121Smrg else: 134fb8a8121Smrg print('DATESTAMP unchanged') 135fb8a8121Smrg 136fb8a8121Smrg 137fb8a8121Smrgif args.current: 138fb8a8121Smrg print('=== Working on the current branch ===', flush=True) 139fb8a8121Smrg update_current_branch() 140fb8a8121Smrgelse: 141fb8a8121Smrg for ref in origin.refs: 142fb8a8121Smrg assert ref.name.startswith('origin/') 143fb8a8121Smrg name = ref.name[len('origin/'):] 144fb8a8121Smrg if name in active_refs: 145fb8a8121Smrg if name in repo.branches: 146fb8a8121Smrg branch = repo.branches[name] 147fb8a8121Smrg else: 148fb8a8121Smrg branch = repo.create_head(name, ref).set_tracking_branch(ref) 149fb8a8121Smrg print('=== Working on: %s ===' % branch, flush=True) 150fb8a8121Smrg branch.checkout() 151fb8a8121Smrg origin.pull(rebase=True) 152fb8a8121Smrg print('branch pulled and checked out') 153a448f87cSmrg update_current_branch(name) 154fb8a8121Smrg assert not repo.index.diff(None) 155fb8a8121Smrg print('branch is done\n', flush=True) 156