Build releases from a commit hash, rather than a named branch.

This modifies the release script to take as its first argument
the hash of the git commit to be released. It also improves the
verification of the previous commit tag by ensuring that the tag
exists in the history of the specified commit.
This commit is contained in:
Kris Nuttycombe 2022-04-14 16:58:38 -06:00
parent f285a39063
commit 91ef609c1b
3 changed files with 105 additions and 70 deletions

View File

@ -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/<DevUser>/zcash
- <DevUser> 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 <RELEASE> <RELEASE_PREV> <APPROX_RELEASE_HEIGHT>
$ ./zcutil/make-release.py --hotfix <COMMIT_ID> <RELEASE> <RELEASE_PREV> <APPROX_RELEASE_HEIGHT>
- To review the automated changes in git:

View File

@ -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, <RELEASE> and <RELEASE_PREV> 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, <RELEASE> and <RELEASE_PREV> must be prefixed with a v, i.e. v1.0.9
(not 1.0.9). <COMMIT_ID> 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 <RELEASE> <RELEASE_PREV> <RELEASE_FROM> <APPROX_RELEASE_HEIGHT>
$ ./zcutil/make-release.py <COMMIT_ID> <RELEASE> <RELEASE_PREV> <RELEASE_FROM> <APPROX_RELEASE_HEIGHT>
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 <keyid>
@ -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

View File

@ -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