diff --git a/doc/hotfix-process.md b/doc/hotfix-process.md new file mode 100644 index 000000000..e6ce8a7a2 --- /dev/null +++ b/doc/hotfix-process.md @@ -0,0 +1,74 @@ +Hotfix Release Process +====================== + +Hotfix releases are versioned by incrementing the build number of the latest +release. For example: + + First hotfix: v1.0.11 -> v1.0.11-1 + Second hotfix: v1.0.11-1 -> v1.0.11-2 + +In the commands below, and are prefixed with a v, ie. +v1.0.11 (not 1.0.11). + +## Create a hotfix branch + +Create a hotfix branch from the previous release tag, and push it to the main +repository: + + $ git branch hotfix- + $ git push 'git@github.com:zcash/zcash' hotfix- + +## Implement hotfix changes + +Hotfix changes are implemented the same way as regular changes (developers work +in separate branches per change, and push the branches to their own repositories), +except that the branches are based on the hotfix branch instead of master: + + $ git checkout hotfix- + $ git checkout -b + +## 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. + +## 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: + + $ ./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 969f5a296..918213cba 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -2,6 +2,8 @@ Release Process ==================== Meta: There should always be a single release engineer to disambiguate responsibility. +If this is a hotfix release, please see `./hotfix-process.md` before proceeding. + ## Pre-release ### Github Milestone @@ -40,6 +42,11 @@ whole engineering team. ## Release process +In the commands below, and are prefixed with a v, ie. +v1.0.9 (not 1.0.9). + +### 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: diff --git a/zcutil/make-release.py b/zcutil/make-release.py index 514d5883f..991fde051 100755 --- a/zcutil/make-release.py +++ b/zcutil/make-release.py @@ -27,6 +27,7 @@ def main(args=sys.argv[1:]): opts.RELEASE_VERSION, opts.RELEASE_PREV, opts.RELEASE_HEIGHT, + opts.HOTFIX, ) except SystemExit as e: logging.error(str(e)) @@ -44,6 +45,12 @@ def parse_args(args): type=str, help='Path to repository root.', ) + p.add_argument( + '--hotfix', + action='store_true', + dest='HOTFIX', + help='Use if this is a hotfix release from a non-master branch.', + ) p.add_argument( 'RELEASE_VERSION', type=Version.parse_arg, @@ -63,9 +70,10 @@ def parse_args(args): # Top-level flow: -def main_logged(release, releaseprev, releaseheight): +def main_logged(release, releaseprev, releaseheight, hotfix): verify_releaseprev_tag(releaseprev) - initialize_git(release) + verify_version(release, releaseprev, hotfix) + initialize_git(release, hotfix) patch_version_in_files(release, releaseprev) patch_release_height(releaseheight) commit('Versioning changes for {}.'.format(release.novtext)) @@ -123,17 +131,41 @@ def verify_releaseprev_tag(releaseprev): ) +@phase('Checking version.') +def verify_version(release, releaseprev, hotfix): + if not hotfix: + return + + expected = Version( + releaseprev.major, + releaseprev.minor, + releaseprev.patch, + releaseprev.betarc, + releaseprev.hotfix + 1 if releaseprev.hotfix else 1, + ) + if release != expected: + raise SystemExit( + "Expected {!r}, given {!r}".format( + expected, release, + ), + ) + + @phase('Initializing git.') -def initialize_git(release): +def initialize_git(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 branch != 'master': + if hotfix: + expected = 'hotfix-' + release.vtext + else: + expected = 'master' + if branch != expected: raise SystemExit( - "Expected branch 'master', found branch {!r}".format( - branch, + "Expected branch {!r}, found branch {!r}".format( + expected, branch, ), )