diff --git a/doc/hotfix-process.md b/doc/hotfix-process.md index 7199cedd5..a849260d1 100644 --- a/doc/hotfix-process.md +++ b/doc/hotfix-process.md @@ -30,30 +30,17 @@ except that the branches are based on the hotfix branch instead of master: ## Merge hotfix PRs Hotfix PRs are created like regular PRs, except using the hotfix branch as the -base instead of master. Each PR should be reviewed as normal, and then the -following process should be used to merge: - -- A CI merge build is manually run by logging into the CI server, going to the - pr-merge builder, clicking the "force" button, and entering the following - values: - - - Repository: https://github.com//zcash - - must be in the set of "safe" users as-specified in the CI - config. - - Branch: name of the hotfix PR branch (not the hotfix release branch). - -- A link to the build and its result is manually added to the PR as a comment. - -- If the build was successful, the PR is merged via the GitHub button. +base instead of `master`. Each PR should be reviewed and merged as normal. ## Release process The majority of this process is identical to the standard release process. However, there are a few notable differences: -- When running the release script, use the `--hotfix` flag: +- When running the release script, use the `--hotfix` flag. Provide the hash of + the commit to be released as the first argument: - $ ./zcutil/make-release.py --hotfix + $ ./zcutil/make-release.py --hotfix - To review the automated changes in git: diff --git a/doc/release-process.md b/doc/release-process.md index 2d81ca596..98fba9756 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -2,7 +2,9 @@ Release Process ==================== Meta: There should always be a single release engineer to disambiguate responsibility. -If this is a hotfix release, please see the [Hotfix Release Process](https://github.com/zcash/zcash/blob/master/doc/hotfix-process.md) documentation before proceeding. +If this is a hotfix release, please see the [Hotfix Release +Process](https://github.com/zcash/zcash/blob/master/doc/hotfix-process.md) documentation +before proceeding. ## Pre-release @@ -63,21 +65,28 @@ progress bar displayed during the build process. ## Release process -In the commands below, and are prefixed with a v, ie. -v1.1.0 (not 1.1.0). +Identify the commit from which the release will be made. This is frequently the current +`HEAD` of `master`, but it's also often useful to instead use a release stabilization +branch based upon a previous release candidate when producing a release, so that +development can proceed unblocked on the `master` branch during the release candidate +testing and bug-fixing process. + +In the commands below, and must be prefixed with a v, i.e. v1.0.9 +(not 1.0.9). is the `git` hash identifying the commit on which the release +branch will be based. It is recommended to use the entire hash value to identify the +commit, although a prefix of at least 10 characters is also permitted. ### Create the release branch -Run the release script, which will verify you are on the latest clean -checkout of master, create a branch, then commit standard automated -changes to that branch locally: +Run the release script, which will create a branch based upon the specified commit ID, +then commit standard automated changes to that branch locally: - $ ./zcutil/make-release.py + $ ./zcutil/make-release.py Examples: - $ ./zcutil/make-release.py v1.1.0 v1.0.0 v1.0.0 120000 - $ ./zcutil/make-release.py v1.1.0 v1.1.0-rc1 v1.0.0 222900 + $ ./zcutil/make-release.py 600c4acee1 v1.1.0-rc1 v1.0.0 v1.0.0 280300 + $ ./zcutil/make-release.py b89b48cda1 v1.1.0 v1.1.0-rc1 v1.0.0 300600 ### Create, Review, and Merge the release branch pull request @@ -89,24 +98,21 @@ Push the resulting branch to github: $ git push 'git@github.com:$YOUR_GITHUB_NAME/zcash' $(git rev-parse --abbrev-ref HEAD) -Then create the PR on github. Complete the standard review process, -then merge, then wait for CI to complete. +Then create the PR on github. Complete the standard review process and wait +for CI to complete. -## Make tag for the newly merged result +## Make tag for the the tip of the release branch -Checkout master and pull the latest version to ensure master is up to date with the release PR which was merged in before. +NOTE: This has changed from the previously recommended process. The tag should be created +at the tip of the release branch; this ensures that any changes made to the `master` +branch since the initiation of the release process are not accidentally tagged as being +part of the release as a consequence of having been included in a merge commit. - $ git checkout master - $ git pull --ff-only - -Check the last commit on the local and remote versions of master to make sure they are the same: +Check the last commit on the local and remote versions of the release branch to make sure +they are the same: $ git log -1 -The output should include something like, which is created by Homu: - - Auto merge of #4242 - nathan-at-least:release-v1.0.9, r=nathan-at-least - If you haven't previously done so, set the gpg key id you intend to use for signing: git config --global user.signingkey @@ -143,16 +149,25 @@ the marking to see what GitHub wants to be done. ## Post Release Task List +### Merge the release branch + +Merge the release branch back to `master` to ensure that any changes made during +release stabilization are reflected in the master branch's history. + ### Deploy testnet -Notify the Zcash DevOps engineer/sysadmin that the release has been tagged. They update some variables in the company's automation code and then run an Ansible playbook, which: +Notify the Zcash DevOps engineer/sysadmin that the release has been tagged. They update +some variables in the company's automation code and then run an Ansible playbook, which: * builds Zcash based on the specified branch * deploys it as a public service (e.g. betatestnet.z.cash, mainnet.z.cash) -* often the same server can be re-used, and the role idempotently handles upgrades, but if not then they also need to update DNS records -* possible manual steps: blowing away the `testnet3` dir, deleting old parameters, restarting DNS seeder +* often the same server can be re-used, and the role idempotently handles upgrades, but if + not then they also need to update DNS records +* possible manual steps: blowing away the `testnet3` dir, deleting old parameters, + restarting DNS seeder -Then, verify that nodes can connect to the testnet server, and update the guide on the wiki to ensure the correct hostname is listed in the recommended zcash.conf. +Then, verify that nodes can connect to the testnet server, and update the guide on the +wiki to ensure the correct hostname is listed in the recommended zcash.conf. ### Update the 1.0 User Guide diff --git a/zcutil/make-release.py b/zcutil/make-release.py index f9e0c79a8..48f76b562 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -24,6 +24,7 @@ def main(args=sys.argv[1:]): try: main_logged( + opts.REVISION, opts.RELEASE_VERSION, opts.RELEASE_PREV, opts.RELEASE_FROM, @@ -52,6 +53,11 @@ def parse_args(args): dest='HOTFIX', help='Use if this is a hotfix release from a non-master branch.', ) + p.add_argument( + 'REVISION', + type=GitHash.parse_arg, + help='The git commit hash from which to construct the release.', + ) p.add_argument( 'RELEASE_VERSION', type=Version.parse_arg, @@ -76,16 +82,16 @@ def parse_args(args): # Top-level flow: -def main_logged(release, releaseprev, releasefrom, releaseheight, hotfix): +def main_logged(revision, release, releaseprev, releasefrom, releaseheight, hotfix): verify_dependencies([ ('help2man', None), ('debchange', 'devscripts'), ]) - verify_tags(releaseprev, releasefrom) + verify_tags(revision, releaseprev, releasefrom) verify_version(release, releaseprev, hotfix) verify_dependency_updates() - initialize_git(release, hotfix) + initialize_git(revision, release, hotfix) patch_version_in_files(release, releaseprev) patch_release_height(releaseheight) commit('Versioning changes for {}.'.format(release.novtext)) @@ -131,10 +137,10 @@ def verify_dependency_updates(): try: sh_log('./qa/zcash/updatecheck.py') except SystemExit: - raise SystemExit("Dependency update check found updates that have not been correctly postponed.") + raise SystemExit("Dependency update check failed. Either some updates not been correctly postponed, or the .updatecheck-token file is missing.") @phase('Checking tags.') -def verify_tags(releaseprev, releasefrom): +def verify_tags(revision, releaseprev, releasefrom): candidates = [] # Any tag beginning with a 'v' followed by [1-9] must be a version @@ -143,7 +149,7 @@ def verify_tags(releaseprev, releasefrom): # ignored. Any other tag is silently ignored. candidatergx = re.compile('^v[1-9].*$') - for tag in sh_out('git', 'tag', '--list').splitlines(): + for tag in sh_out('git', 'tag', '--list', '--merged', revision.value).splitlines(): if candidatergx.match(tag): v = Version.parse(tag) if v is not None: @@ -153,12 +159,18 @@ def verify_tags(releaseprev, releasefrom): try: latest = candidates[-1] except IndexError: - raise SystemExit('No previous releases found by `git tag --list`.') + raise SystemExit( + 'No previous releases found by `git tag --list --merged {}`.' + .format( + revision.value + ), + ) if releaseprev != latest: raise SystemExit( - 'The latest candidate in `git tag --list` is {} not {}' + 'The latest candidate in `git tag --list --merged {} is {} not {}' .format( + revision.value, latest.vtext, releaseprev.vtext, ), @@ -173,9 +185,10 @@ def verify_tags(releaseprev, releasefrom): prev_tags.append(candidate) else: raise SystemExit( - '{} does not appear in `git tag --list`' + '{} does not appear in `git tag --list --merged {}`' .format( releasefrom.vtext, + revision.value, ), ) @@ -211,29 +224,20 @@ def verify_version(release, releaseprev, hotfix): @phase('Initializing git.') -def initialize_git(release, hotfix): +def initialize_git(revision, release, hotfix): junk = sh_out('git', 'status', '--porcelain') if junk.strip(): raise SystemExit('There are uncommitted changes:\n' + junk) - branch = sh_out('git', 'rev-parse', '--abbrev-ref', 'HEAD').strip() - if hotfix: - expected = 'hotfix-' + release.vtext - else: - expected = 'master' - if branch != expected: - raise SystemExit( - "Expected branch {!r}, found branch {!r}".format( - expected, branch, - ), - ) - - logging.info('Ensuring we are up to date with the branch to be released...') - sh_log('git', 'pull', '--ff-only') - branch = 'release-' + release.vtext - logging.info('Creating release branch: %r', branch) - sh_log('git', 'checkout', '-b', branch) + logging.info( + 'Creating release branch {} from revision {}.' + .format( + branch, + revision.value + ) + ) + sh_log('git', 'checkout', '-b', branch, revision.value) return branch @@ -481,6 +485,30 @@ def sh_progress(markers, *args): if status != 0: raise SystemExit('Nonzero exit status: {!r}'.format(status)) +class GitHash (object): + '''A git commit hash.''' + RGX = re.compile( + r'^([0-9a-f]{8,40})$', + ) + + @staticmethod + def parse_arg(text): + m = GitHash.RGX.match(text) + if m is None: + raise argparse.ArgumentTypeError( + 'Could not parse revision {!r} against regex {}'.format( + text, + GitHash.RGX.pattern, + ), + ) + else: + assert len(m.groups()) == 1 + [value] = m.groups() + return GitHash(value) + + def __init__(self, value): + assert GitHash.RGX.match(value) is not None + self.value = value class Version (object): '''A release version.''' @@ -652,6 +680,11 @@ class TestVersion (unittest.TestCase): v = Version.parse_arg(case) self.assertEqual(v.vtext, case) + def test_rev_parse(self): + sample = '958bcf2dac6d81d17797c0f58f176262a496cfd4' + rev = GitHash.parse_arg(sample) + self.assertEqual(rev.value, sample) + def test_arg_parse_negatives(self): cases = [ 'v07.0.0', @@ -697,7 +730,7 @@ if __name__ == '__main__': print('=== Self Test ===') try: - unittest.main() + unittest.main(verbosity=2) except SystemExit as e: if e.args[0] != 0: raise