diff --git a/doc/hotfix-process.md b/doc/hotfix-process.md index 7199cedd5..1dd60d7ae 100644 --- a/doc/hotfix-process.md +++ b/doc/hotfix-process.md @@ -30,45 +30,20 @@ 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: +Release candidates for hotfixes should be created and tested as normal, using +the `hotfix-` branch in place of the release stabilization branch, +with a couple of minor 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: $ git log hotfix-..HEAD - -- After the standard review process, use the hotfix merge process outlined above - instead of the regular merge process. - -- When making the tag, check out the hotfix branch instead of master. - -## Post-release - -Once the hotfix release has been created, a new PR should be opened for merging -the hotfix release branch into master. This may require fixing merge conflicts -(e.g. changing the version number in the hotfix branch to match master, if -master is ahead). Such conflicts **MUST** be addressed with additional commits -to the hotfix branch; specifically, the branch **MUST NOT** be rebased on -master. diff --git a/doc/release-process.md b/doc/release-process.md index 2d81ca596..d4fc3bd1a 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 @@ -61,62 +63,105 @@ The release script has the following dependencies: You can optionally install the `progressbar2` Python module with pip to have a progress bar displayed during the build process. -## Release process +## Versioning -In the commands below, and are prefixed with a v, ie. -v1.1.0 (not 1.1.0). +Zcash version identifiers have the format `vX.Y.Z` with the following conventions: -### Create the release branch +* Increments to the `X` component (the "major version") correspond to network + upgrades. A network upgrade occurs only when there is a change to the + consensus rules. +* Increments to the `Y` component (the "minor version") correspond to regular + Zcash releases. These occur approximately every 6 weeks and may include breaking + changes to public APIs. +* Increments to the `Z` component occur only in the case of hotfix releases. -Run the release script, which will verify you are on the latest clean -checkout of master, create a branch, then commit standard automated +## Release candidate & release process + +Identify the commit from which the release stabilization branch will be made. +Release stabilization branches are used so that development can proceed +unblocked on the `master` branch during the release candidate testing and +bug-fixing process. By convention, release stabilization branches are named +`version-X.Y.0` where `X` and `Y` are the major and minor versions for the +release. + +In the commands below, and must identify `git` tags +prefixed with the character `v`, i.e. `v1.0.9` (not `1.0.9`). is a +`git` hash identifying the commit on which a release stabilization or 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 stabilization branch + +Having identified the commit from which the release will be made, the release +manager constructs the release stabilization branch as follows: + + $ git checkout -b version-X.Y.0 + $ git push 'git@github.com:zcash/zcash' $(git rev-parse --abrev-ref HEAD) + +### Create the release candidate branch + +Run the release script to create the first release candidate. This 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 Review the automated changes in git: - $ git log master..HEAD + $ git log version-X.Y.0..HEAD 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 targeting the `version-X.Y.0` branch. Complete the +standard review process and wait for CI to complete. -## Make tag for the newly merged result +## Make a tag for the tip of the release candidate 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 automatically-generated release branch created by +the release script; this ensures that any changes made to the release +stabilization 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: +If you haven't previously done so, set the gpg key id you intend to use for +signing: git config --global user.signingkey -Then create the git tag. The `-s` means the release tag will be signed. -Enter "Release ." and save when prompted for a commit message. -**CAUTION:** Remember the `v` at the beginning here: +Then create the git tag. The `-s` means the release tag will be signed. Enter +"Release ." and save when prompted for a commit message. **CAUTION:** +Remember the `v` at the beginning here: - $ git tag -s v1.1.0 - $ git push origin v1.1.0 + $ git tag -s vX.Y.Z-rcN + $ git push origin vX.Y.Z-rcN + +## Merge the release candidate branch to the release stabilization branch + +Once CI has completed and the release candidate branch has sufficient approving +reviews, merge the release candidate branch back to the release stabilization +branch. Testing proceeds as normal. Any changes that need to be made during the +release candidate period are made by submitting PRs targeting the release +stabilization branch. + +Subsequent release candidates, and the creation of the final release, follow +the same process as for release candidates, omitting the `-rcN` suffix for the +final release. ## Make and deploy deterministic builds @@ -143,22 +188,35 @@ the marking to see what GitHub wants to be done. ## Post Release Task List +### Merge the release stabilization branch + +Once the final release branch has merged to the release stabilization branch, a +new PR should be opened for merging the release stabilization branch into +master. This may require fixing merge conflicts (e.g. changing the version +number in the release stabilization branch to match master, if master is +ahead). Such conflicts **MUST** be addressed with additional commits to the +release stabilization branch; specifically, the branch **MUST NOT** be rebased +on master. + +Once any conflicts have been resolved, the release stabilization branch should +be merged back to the `master` branch, and then deleted. + ### 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 +* deploys it as a public service (e.g. testnet.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. -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. +Verify that nodes can connect to the mainnet and testnet servers. -### Update the 1.0 User Guide - -This also means updating [the translations](https://github.com/zcash/zcash-docs). -Coordinate with the translation team for now. Suggestions for improving this -part of the process should be added to #2596. +Update the [Zcashd Full Node and CLI](https://zcash.readthedocs.io/en/latest/rtd_pages/zcashd.html) +documentation on ReadTheDocs to give the new version number. ### Publish the release announcement (blog, github, zcash-dev, slack) diff --git a/zcutil/make-release.py b/zcutil/make-release.py index f9e0c79a8..d0082bfa4 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 have 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]{10,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