diff options
Diffstat (limited to 'tools/dist/create-minor-release-branch.py')
-rwxr-xr-x | tools/dist/create-minor-release-branch.py | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/tools/dist/create-minor-release-branch.py b/tools/dist/create-minor-release-branch.py new file mode 100755 index 0000000..a68fa23 --- /dev/null +++ b/tools/dist/create-minor-release-branch.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python +# python: coding=utf-8 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +# About this script: +# This script is intended to automate steps in creating a new Subversion +# minor release. + +import os +import re +import sys +import logging +import subprocess +import argparse # standard in Python 2.7 + +from release import Version + + +# Some constants +repos = 'https://svn.apache.org/repos/asf/subversion' +secure_repos = 'https://svn.apache.org/repos/asf/subversion' +buildbot_repos = 'https://svn.apache.org/repos/infra/infrastructure/buildbot/aegis/buildmaster' + +# Parameters +dry_run = False + +# Local working copies +base_dir = None # set by main() + +def get_trunk_wc_path(path=None): + trunk_wc_path = os.path.join(base_dir, 'svn-trunk') + if path is None: return trunk_wc_path + return os.path.join(trunk_wc_path, path) +def get_branch_wc_path(ver, path=None): + branch_wc_path = os.path.join(base_dir, ver.branch + '.x') + if path is None: return branch_wc_path + return os.path.join(branch_wc_path, path) +def get_buildbot_wc_path(path=None): + buildbot_wc_path = os.path.join(base_dir, 'svn-buildmaster') + if path is None: return buildbot_wc_path + return os.path.join(buildbot_wc_path, path) + +def get_trunk_url(): + return secure_repos + '/trunk' +def get_branch_url(ver): + return secure_repos + '/branches/' + ver.branch + '.x' +def get_tag_url(ver): + return secure_repos + '/tags/' + ver.base +def get_buildbot_url(): + return buildbot_repos + +#---------------------------------------------------------------------- +# Utility functions + +def run(cmd, dry_run=False): + print('+ ' + ' '.join(cmd)) + if not dry_run: + stdout = subprocess.check_output(cmd) + print(stdout) + else: + print(' ## dry-run; not executed') + +def run_svn(cmd, dry_run=False): + run(['svn'] + cmd, dry_run) + +def svn_commit(cmd): + run_svn(['commit'] + cmd, dry_run=dry_run) + +def svn_copy_branch(src, dst, message): + args = ['copy', src, dst, '-m', message] + run_svn(args, dry_run=dry_run) + +def svn_checkout(url, wc, *args): + args = ['checkout', url, wc] + list(args) + run_svn(args) + +#---------------------------------------------------------------------- +def edit_file(path, pattern, replacement): + print("Editing '%s'" % (path,)) + print(" pattern='%s'" % (pattern,)) + print(" replace='%s'" % (replacement,)) + old_text = open(path, 'r').read() + new_text = re.sub(pattern, replacement, old_text) + assert new_text != old_text + open(path, 'w').write(new_text) + +def edit_changes_file(path, newtext): + """Insert NEWTEXT in the 'CHANGES' file found at PATH, + just before the first line that starts with 'Version '. + """ + print("Prepending to '%s'" % (path,)) + print(" text='%s'" % (newtext,)) + lines = open(path, 'r').readlines() + for i, line in enumerate(lines): + if line.startswith('Version '): + with open(path, 'w') as newfile: + newfile.writelines(lines[:i]) + newfile.write(newtext) + newfile.writelines(lines[i:]) + break + +#---------------------------------------------------------------------- +def make_release_branch(ver, revnum): + svn_copy_branch(get_trunk_url() + '@' + (str(revnum) if revnum else ''), + get_branch_url(ver), + 'Create the ' + ver.branch + '.x release branch.') + +#---------------------------------------------------------------------- +def update_minor_ver_in_trunk(ver, revnum): + """Change the minor version in trunk to the next (future) minor version. + """ + trunk_wc = get_trunk_wc_path() + trunk_url = get_trunk_url() + svn_checkout(trunk_url + '@' + (str(revnum) if revnum else ''), + trunk_wc) + + prev_ver = Version('1.%d.0' % (ver.minor - 1,)) + next_ver = Version('1.%d.0' % (ver.minor + 1,)) + relpaths = [] + + relpath = 'subversion/include/svn_version.h' + relpaths.append(relpath) + edit_file(get_trunk_wc_path(relpath), + r'(#define SVN_VER_MINOR *)%s' % (ver.minor,), + r'\g<1>%s' % (next_ver.minor,)) + + relpath = 'subversion/tests/cmdline/svntest/main.py' + relpaths.append(relpath) + edit_file(get_trunk_wc_path(relpath), + r'(SVN_VER_MINOR = )%s' % (ver.minor,), + r'\g<1>%s' % (next_ver.minor,)) + + relpath = 'subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java' + relpaths.append(relpath) + try: + # since r1817921 (just after branching 1.10) + edit_file(get_trunk_wc_path(relpath), + r'SVN_VER_MINOR = %s;' % (ver.minor,), + r'SVN_VER_MINOR = %s;' % (next_ver.minor,)) + except: + # before r1817921: two separate places + edit_file(get_trunk_wc_path(relpath), + r'version.isAtLeast\(1, %s, 0\)' % (ver.minor,), + r'version.isAtLeast\(1, %s, 0\)' % (next_ver.minor,)) + edit_file(get_trunk_wc_path(relpath), + r'1.%s.0, but' % (ver.minor,), + r'1.%s.0, but' % (next_ver.minor,)) + + relpath = 'CHANGES' + relpaths.append(relpath) + # insert at beginning of CHANGES file + edit_changes_file(get_trunk_wc_path(relpath), + 'Version ' + next_ver.base + '\n' + + '(?? ??? 20XX, from /branches/' + next_ver.branch + '.x)\n' + + get_tag_url(next_ver) + '\n' + + '\n') + + log_msg = '''\ +Increment the trunk version number to %s, and introduce a new CHANGES +section, following the creation of the %s.x release branch. + +* subversion/include/svn_version.h, + subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java, + subversion/tests/cmdline/svntest/main.py + (SVN_VER_MINOR): Increment to %s. + +* CHANGES: New section for %s.0. +''' % (next_ver.branch, ver.branch, next_ver.minor, next_ver.branch) + commit_paths = [get_trunk_wc_path(p) for p in relpaths] + svn_commit(commit_paths + ['-m', log_msg]) + +#---------------------------------------------------------------------- +def create_status_file_on_branch(ver): + branch_wc = get_branch_wc_path(ver) + branch_url = get_branch_url(ver) + svn_checkout(branch_url, branch_wc, '--depth=immediates') + + status_local_path = os.path.join(branch_wc, 'STATUS') + text='''\ + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * THIS RELEASE STREAM IS OPEN FOR STABILIZATION. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +This file tracks the status of releases in the %s.x line. + +See http://subversion.apache.org/docs/community-guide/releasing.html#release-stabilization +for details on how release lines and voting work, what kinds of bugs can +delay a release, etc. + +Status of %s: + +Candidate changes: +================== + + +Veto-blocked changes: +===================== + + +Approved changes: +================= +''' % (ver.branch, ver.base) + open(status_local_path, 'wx').write(text) + run_svn(['add', status_local_path]) + svn_commit([status_local_path, + '-m', '* branches/' + ver.branch + '.x/STATUS: New file.']) + +#---------------------------------------------------------------------- +def update_backport_bot(ver): + print("""MANUAL STEP: Fork & edit & pull-request on GitHub: +https://github.com/apache/infrastructure-puppet/blob/deployment/modules/svnqavm_pvm_asf/manifests/init.pp +"Add new %s.x branch to list of backport branches" +""" % (ver.branch,)) + print("""Someone needs to run the 'svn checkout' manually. +The exact checkout command is documented in machines/svn-qavm2/notes.txt +in the private repository (need to use a trunk client and the svn-master.a.o +hostname). +""") + +#---------------------------------------------------------------------- +def update_buildbot_config(ver): + """Add the new branch to the list of branches monitored by the buildbot + master. + """ + buildbot_wc = get_buildbot_wc_path() + buildbot_url = get_buildbot_url() + svn_checkout(buildbot_url, buildbot_wc) + + prev_ver = Version('1.%d.0' % (ver.minor - 1,)) + next_ver = Version('1.%d.0' % (ver.minor + 1,)) + + relpath = 'master1/projects/subversion.conf' + edit_file(get_buildbot_wc_path(relpath), + r'(MINOR_LINES=\[.*%s)(\])' % (prev_ver.minor,), + r'\1, %s\2' % (ver.minor,)) + + log_msg = '''\ +Subversion: start monitoring the %s branch. +''' % (ver.branch) + commit_paths = [get_buildbot_wc_path(relpath)] + svn_commit(commit_paths + ['-m', log_msg]) + +#---------------------------------------------------------------------- +def create_release_branch(args): + make_release_branch(args.version, args.revnum) + update_minor_ver_in_trunk(args.version, args.revnum) + create_status_file_on_branch(args.version) + update_backport_bot(args.version) + update_buildbot_config(args.version) + + +#---------------------------------------------------------------------- +# Main entry point for argument parsing and handling + +def main(): + 'Parse arguments, and drive the appropriate subcommand.' + + # Setup our main parser + parser = argparse.ArgumentParser( + description='Create an Apache Subversion release branch.') + subparsers = parser.add_subparsers(title='subcommands') + + # Setup the parser for the create-release-branch subcommand + subparser = subparsers.add_parser('create-release-branch', + help='''Create a minor release branch: branch from trunk, + update version numbers on trunk, create status + file on branch, update backport bot, + update buildbot config.''') + subparser.set_defaults(func=create_release_branch) + subparser.add_argument('version', type=Version, + help='''A version number to indicate the branch, such as + '1.7.0' (the '.0' is required).''') + subparser.add_argument('revnum', type=lambda arg: int(arg.lstrip('r')), + nargs='?', default=None, + help='''The trunk revision number to base the branch on. + Default is HEAD.''') + subparser.add_argument('--dry-run', action='store_true', default=False, + help='Avoid committing any changes to repositories.') + subparser.add_argument('--verbose', action='store_true', default=False, + help='Increase output verbosity') + subparser.add_argument('--base-dir', default=os.getcwd(), + help='''The directory in which to create needed files and + folders. The default is the current working + directory.''') + + # Parse the arguments + args = parser.parse_args() + + global base_dir, dry_run + base_dir = args.base_dir + dry_run = args.dry_run + + # Set up logging + logger = logging.getLogger() + if args.verbose: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + # Make timestamps in tarballs independent of local timezone + os.environ['TZ'] = 'UTC' + + # finally, run the subcommand, and give it the parsed arguments + args.func(args) + + +if __name__ == '__main__': + main() |