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:
parent
f285a39063
commit
91ef609c1b
|
@ -30,30 +30,17 @@ except that the branches are based on the hotfix branch instead of master:
|
||||||
## Merge hotfix PRs
|
## Merge hotfix PRs
|
||||||
|
|
||||||
Hotfix PRs are created like regular PRs, except using the hotfix branch as the
|
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
|
base instead of `master`. Each PR should be reviewed and merged as normal.
|
||||||
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.
|
|
||||||
|
|
||||||
## Release process
|
## Release process
|
||||||
|
|
||||||
The majority of this process is identical to the standard release process.
|
The majority of this process is identical to the standard release process.
|
||||||
However, there are a few notable differences:
|
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:
|
- To review the automated changes in git:
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ Release Process
|
||||||
====================
|
====================
|
||||||
Meta: There should always be a single release engineer to disambiguate responsibility.
|
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
|
## Pre-release
|
||||||
|
|
||||||
|
@ -63,21 +65,28 @@ progress bar displayed during the build process.
|
||||||
|
|
||||||
## Release process
|
## Release process
|
||||||
|
|
||||||
In the commands below, <RELEASE> and <RELEASE_PREV> are prefixed with a v, ie.
|
Identify the commit from which the release will be made. This is frequently the current
|
||||||
v1.1.0 (not 1.1.0).
|
`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
|
### Create the release branch
|
||||||
|
|
||||||
Run the release script, which will verify you are on the latest clean
|
Run the release script, which will create a branch based upon the specified commit ID,
|
||||||
checkout of master, create a branch, then commit standard automated
|
then commit standard automated changes to that branch locally:
|
||||||
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:
|
Examples:
|
||||||
|
|
||||||
$ ./zcutil/make-release.py v1.1.0 v1.0.0 v1.0.0 120000
|
$ ./zcutil/make-release.py 600c4acee1 v1.1.0-rc1 v1.0.0 v1.0.0 280300
|
||||||
$ ./zcutil/make-release.py v1.1.0 v1.1.0-rc1 v1.0.0 222900
|
$ ./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
|
### 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)
|
$ 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 create the PR on github. Complete the standard review process and wait
|
||||||
then merge, then wait for CI to complete.
|
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
|
Check the last commit on the local and remote versions of the release branch to make sure
|
||||||
$ git pull --ff-only
|
they are the same:
|
||||||
|
|
||||||
Check the last commit on the local and remote versions of master to make sure they are the same:
|
|
||||||
|
|
||||||
$ git log -1
|
$ 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 <keyid>
|
git config --global user.signingkey <keyid>
|
||||||
|
@ -143,16 +149,25 @@ the marking to see what GitHub wants to be done.
|
||||||
|
|
||||||
## Post Release Task List
|
## 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
|
### 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
|
* builds Zcash based on the specified branch
|
||||||
* deploys it as a public service (e.g. betatestnet.z.cash, mainnet.z.cash)
|
* 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
|
* often the same server can be re-used, and the role idempotently handles upgrades, but if
|
||||||
* possible manual steps: blowing away the `testnet3` dir, deleting old parameters, restarting DNS seeder
|
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
|
### Update the 1.0 User Guide
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ def main(args=sys.argv[1:]):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
main_logged(
|
main_logged(
|
||||||
|
opts.REVISION,
|
||||||
opts.RELEASE_VERSION,
|
opts.RELEASE_VERSION,
|
||||||
opts.RELEASE_PREV,
|
opts.RELEASE_PREV,
|
||||||
opts.RELEASE_FROM,
|
opts.RELEASE_FROM,
|
||||||
|
@ -52,6 +53,11 @@ def parse_args(args):
|
||||||
dest='HOTFIX',
|
dest='HOTFIX',
|
||||||
help='Use if this is a hotfix release from a non-master branch.',
|
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(
|
p.add_argument(
|
||||||
'RELEASE_VERSION',
|
'RELEASE_VERSION',
|
||||||
type=Version.parse_arg,
|
type=Version.parse_arg,
|
||||||
|
@ -76,16 +82,16 @@ def parse_args(args):
|
||||||
|
|
||||||
|
|
||||||
# Top-level flow:
|
# Top-level flow:
|
||||||
def main_logged(release, releaseprev, releasefrom, releaseheight, hotfix):
|
def main_logged(revision, release, releaseprev, releasefrom, releaseheight, hotfix):
|
||||||
verify_dependencies([
|
verify_dependencies([
|
||||||
('help2man', None),
|
('help2man', None),
|
||||||
('debchange', 'devscripts'),
|
('debchange', 'devscripts'),
|
||||||
])
|
])
|
||||||
|
|
||||||
verify_tags(releaseprev, releasefrom)
|
verify_tags(revision, releaseprev, releasefrom)
|
||||||
verify_version(release, releaseprev, hotfix)
|
verify_version(release, releaseprev, hotfix)
|
||||||
verify_dependency_updates()
|
verify_dependency_updates()
|
||||||
initialize_git(release, hotfix)
|
initialize_git(revision, release, hotfix)
|
||||||
patch_version_in_files(release, releaseprev)
|
patch_version_in_files(release, releaseprev)
|
||||||
patch_release_height(releaseheight)
|
patch_release_height(releaseheight)
|
||||||
commit('Versioning changes for {}.'.format(release.novtext))
|
commit('Versioning changes for {}.'.format(release.novtext))
|
||||||
|
@ -131,10 +137,10 @@ def verify_dependency_updates():
|
||||||
try:
|
try:
|
||||||
sh_log('./qa/zcash/updatecheck.py')
|
sh_log('./qa/zcash/updatecheck.py')
|
||||||
except SystemExit:
|
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.')
|
@phase('Checking tags.')
|
||||||
def verify_tags(releaseprev, releasefrom):
|
def verify_tags(revision, releaseprev, releasefrom):
|
||||||
candidates = []
|
candidates = []
|
||||||
|
|
||||||
# Any tag beginning with a 'v' followed by [1-9] must be a version
|
# 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.
|
# ignored. Any other tag is silently ignored.
|
||||||
candidatergx = re.compile('^v[1-9].*$')
|
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):
|
if candidatergx.match(tag):
|
||||||
v = Version.parse(tag)
|
v = Version.parse(tag)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
|
@ -153,12 +159,18 @@ def verify_tags(releaseprev, releasefrom):
|
||||||
try:
|
try:
|
||||||
latest = candidates[-1]
|
latest = candidates[-1]
|
||||||
except IndexError:
|
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:
|
if releaseprev != latest:
|
||||||
raise SystemExit(
|
raise SystemExit(
|
||||||
'The latest candidate in `git tag --list` is {} not {}'
|
'The latest candidate in `git tag --list --merged {} is {} not {}'
|
||||||
.format(
|
.format(
|
||||||
|
revision.value,
|
||||||
latest.vtext,
|
latest.vtext,
|
||||||
releaseprev.vtext,
|
releaseprev.vtext,
|
||||||
),
|
),
|
||||||
|
@ -173,9 +185,10 @@ def verify_tags(releaseprev, releasefrom):
|
||||||
prev_tags.append(candidate)
|
prev_tags.append(candidate)
|
||||||
else:
|
else:
|
||||||
raise SystemExit(
|
raise SystemExit(
|
||||||
'{} does not appear in `git tag --list`'
|
'{} does not appear in `git tag --list --merged {}`'
|
||||||
.format(
|
.format(
|
||||||
releasefrom.vtext,
|
releasefrom.vtext,
|
||||||
|
revision.value,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -211,29 +224,20 @@ def verify_version(release, releaseprev, hotfix):
|
||||||
|
|
||||||
|
|
||||||
@phase('Initializing git.')
|
@phase('Initializing git.')
|
||||||
def initialize_git(release, hotfix):
|
def initialize_git(revision, release, hotfix):
|
||||||
junk = sh_out('git', 'status', '--porcelain')
|
junk = sh_out('git', 'status', '--porcelain')
|
||||||
if junk.strip():
|
if junk.strip():
|
||||||
raise SystemExit('There are uncommitted changes:\n' + junk)
|
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
|
branch = 'release-' + release.vtext
|
||||||
logging.info('Creating release branch: %r', branch)
|
logging.info(
|
||||||
sh_log('git', 'checkout', '-b', branch)
|
'Creating release branch {} from revision {}.'
|
||||||
|
.format(
|
||||||
|
branch,
|
||||||
|
revision.value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sh_log('git', 'checkout', '-b', branch, revision.value)
|
||||||
return branch
|
return branch
|
||||||
|
|
||||||
|
|
||||||
|
@ -481,6 +485,30 @@ def sh_progress(markers, *args):
|
||||||
if status != 0:
|
if status != 0:
|
||||||
raise SystemExit('Nonzero exit status: {!r}'.format(status))
|
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):
|
class Version (object):
|
||||||
'''A release version.'''
|
'''A release version.'''
|
||||||
|
@ -652,6 +680,11 @@ class TestVersion (unittest.TestCase):
|
||||||
v = Version.parse_arg(case)
|
v = Version.parse_arg(case)
|
||||||
self.assertEqual(v.vtext, 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):
|
def test_arg_parse_negatives(self):
|
||||||
cases = [
|
cases = [
|
||||||
'v07.0.0',
|
'v07.0.0',
|
||||||
|
@ -697,7 +730,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
print('=== Self Test ===')
|
print('=== Self Test ===')
|
||||||
try:
|
try:
|
||||||
unittest.main()
|
unittest.main(verbosity=2)
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
if e.args[0] != 0:
|
if e.args[0] != 0:
|
||||||
raise
|
raise
|
||||||
|
|
Loading…
Reference in New Issue