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