| # Copyright 2021 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Apply a tagged, empty commit to an integration.git release branch. |
| |
| The tag increments the candidate version at branch HEAD, i.e. if branch HEAD is |
| A.B.C.D, then the new tag is A.B.C.D+1. |
| |
| This recipe can be used to activate LUCI schedulers watching release refs in |
| Gitiles e.g. to retry failed builds free of side-effects (unlike cherry-picks). |
| |
| Options |
| - Dryrun: Run recipe without modifying anything remotely. |
| """ |
| |
| from PB.recipes.fuchsia.release.increment_version import InputProperties |
| |
| PYTHON_VERSION_COMPATIBILITY = "PY3" |
| |
| DEPS = [ |
| "fuchsia/git", |
| "fuchsia/git_checkout", |
| "fuchsia/release", |
| "fuchsia/sso", |
| "fuchsia/status_check", |
| "recipe_engine/context", |
| "recipe_engine/properties", |
| "recipe_engine/raw_io", |
| "recipe_engine/step", |
| ] |
| |
| PROPERTIES = InputProperties |
| |
| COMMIT_MESSAGE = "{prefix} Increment release version to {version}\n\nBug: {bug}" |
| |
| |
| def RunSteps(api, props): |
| with api.step.nest("check inputs"): |
| if not props.remote: |
| api.step.empty( |
| "missing remote input", |
| step_text="`remote` must be specified", |
| status=api.step.FAILURE, |
| ) |
| if not props.bug: |
| api.step.empty( |
| "missing bug input", |
| step_text="`bug` must be specified", |
| status=api.step.FAILURE, |
| ) |
| if not api.release.validate_branch(props.target_branch): |
| api.step.empty( |
| "invalid target branch input", |
| step_text='`target_branch` must start with "releases"', |
| status=api.step.FAILURE, |
| ) |
| https_remote = api.sso.sso_to_https(props.remote) |
| revision = api.git.get_remote_branch_head( |
| step_name="get target branch HEAD", |
| url=https_remote, |
| branch=props.target_branch, |
| ) |
| if not revision: |
| api.step.empty( |
| "target branch does not exist", |
| step_text="target branch %s does not exist" % props.target_branch, |
| status=api.step.FAILURE, |
| ) |
| |
| checkout_root, _ = api.git_checkout( |
| https_remote, |
| revision=revision, |
| cache=False, |
| step_name="checkout base revision", |
| ) |
| |
| with api.step.nest("resolve release version"): |
| release_version = api.release.get_next_candidate_version( |
| ref=revision, |
| repo_path=checkout_root, |
| ) |
| if api.git.get_remote_tag(url=https_remote, tag=release_version.tag_name): |
| api.step.empty( |
| "release version conflict", |
| step_text=( |
| "attempting increment would conflict with existing " |
| "version %s, indicating source revision %s has already moved forward" |
| % (release_version.tag_name, revision) |
| ), |
| status=api.step.FAILURE, |
| ) |
| |
| with api.context(cwd=checkout_root), api.step.nest( |
| "create release version" |
| ) as presentation: |
| commit_message = COMMIT_MESSAGE.format( |
| prefix=api.release._COMMIT_MESSAGE_PREFIX, |
| version=str(release_version), |
| bug=props.bug, |
| ) |
| api.git.commit( |
| step_name="create empty commit", |
| message=commit_message, |
| allow_empty=True, |
| ) |
| api.git.tag( |
| step_name="tag release", |
| name=str(release_version.tag_name), |
| ) |
| api.git.push( |
| step_name="push release", |
| refs=[ |
| "HEAD:refs/heads/%s" % props.target_branch, |
| "refs/tags/%s" % release_version.tag_name, |
| ], |
| atomic=True, |
| dryrun=props.dryrun, |
| ) |
| release_revision = api.git.get_hash() |
| api.release.set_output_properties( |
| presentation, |
| release_revision, |
| release_version, |
| ) |
| |
| if not props.dryrun and props.downstream_builders: |
| api.release.lookup_builds( |
| builders=props.downstream_builders, |
| revision=release_revision, |
| remote=https_remote, |
| ) |
| |
| |
| def GenTests(api): |
| default_branch = "releases/targetbranch" |
| default_remote = "sso://fuchsia/integration" |
| default_bug = "123456" |
| empty_output = api.raw_io.stream_output_text("") |
| |
| yield api.status_check.test("missing_remote_input", status="failure") |
| yield ( |
| api.status_check.test("missing_bug_input", status="failure") |
| + api.properties( |
| remote=default_remote, |
| ) |
| ) |
| yield ( |
| api.status_check.test("invalid_branch_input", status="failure") |
| + api.properties( |
| target_branch="invalidbranch", |
| remote=default_remote, |
| bug=default_bug, |
| ) |
| ) |
| yield ( |
| api.status_check.test("branch_does_not_exist", status="failure") |
| + api.properties( |
| target_branch=default_branch, |
| remote=default_remote, |
| bug=default_bug, |
| ) |
| + api.step_data( |
| "check inputs.get target branch HEAD", |
| empty_output, |
| ) |
| ) |
| yield ( |
| api.status_check.test("release_version_conflict", status="failure") |
| + api.properties( |
| target_branch=default_branch, |
| remote=default_remote, |
| bug=default_bug, |
| ) |
| + api.release.ref_to_release_version( |
| "releases/0.20200102.0.1", |
| nesting="resolve release version", |
| ) |
| ) |
| yield ( |
| api.status_check.test("increment_version") |
| + api.properties( |
| target_branch=default_branch, |
| remote=default_remote, |
| bug=default_bug, |
| downstream_builders=["a/b/c"], |
| ) |
| + api.release.ref_to_release_version( |
| "releases/0.20200102.0.1", |
| nesting="resolve release version", |
| ) |
| + api.step_data( |
| "resolve release version.get remote tag", |
| empty_output, |
| ) |
| ) |