def determine_release_tag(ctx: TagContext) -> None: click.secho("> Determining what the release tag should be.", fg="cyan") head_ref = ctx.release_pr["head"]["ref"] match = re.match(r"release-.+-(v\d+\.\d+\.\d+)", head_ref) if match is not None: ctx.release_tag = match.group(1) else: click.secho( "I couldn't determine what the release tag should be from the PR's" f"head ref {head_ref}.", fg="red", ) ctx.release_tag = click.prompt( "What should the release tag be (for example, storage-1.2.3)?" ) click.secho(f"Release tag is {ctx.release_tag}.")
def tag(ctx: TagContext = None) -> TagContext: if not ctx: ctx = TagContext() if ctx.interactive: click.secho( f"o/ Hey, {getpass.getuser()}, let's tag a Ruby release!", fg="magenta" ) if ctx.github is None: releasetool.commands.common.setup_github_context(ctx) if ctx.release_pr is None: determine_release_pr(ctx) determine_release_tag(ctx) determine_package_name_and_version(ctx) # If the release already exists, don't do anything if releasetool.commands.common.release_exists(ctx): click.secho(f"{ctx.release_tag} already exists.", fg="magenta") return ctx get_release_notes(ctx) create_release(ctx) ctx.kokoro_job_name = kokoro_job_name(ctx.upstream_repo, ctx.package_name) releasetool.commands.common.publish_via_kokoro(ctx) if ctx.interactive: click.secho("\\o/ All done!", fg="magenta") branch = ctx.release_pr["head"]["ref"] try: ctx.github.delete_branch(repository=ctx.upstream_repo, branch=branch) click.secho(f"Deleted branch {branch}") # If user has already deleted the branch, this will fail. except HTTPError as exc: if exc.response.status_code != 422: click.secho(f"{exc!r}") return ctx
def get_release_notes(ctx: TagContext) -> None: click.secho("> Grabbing the release notes.", fg="cyan") changelog = ctx.github.get_contents( ctx.upstream_repo, f"{ctx.package_name}/CHANGELOG.md", ref=ctx.release_pr["merge_commit_sha"], ).decode("utf-8") match = re.search( rf"^### {ctx.release_version} \/ \d\d\d\d-\d\d-\d\d\n(?P<notes>.+?)(\n###\s|\Z)", changelog, re.DOTALL | re.MULTILINE, ) if match is not None: ctx.release_notes = match.group("notes").strip() else: ctx.release_notes = "" click.secho(f"Here's the release notes:\n\n{ctx.release_notes}\n")
def test_new_style_release_notes_patch(): """ In the conventional-commits template (see: https://github.com/conventional-changelog/conventional-changelog), patches are an H3 header and are linked to the underlying issue that created the release. """ expected = """### Bug Fixes * include 'x-goog-request-params' header in requests ([#167](https://www.github.com/googleapis/nodejs-os-login/issues/167)) ([074051d](https://www.github.com/googleapis/nodejs-os-login/commit/074051d))""" ctx = TagContext(release_version="v0.3.3") _get_latest_release_notes(ctx, fixture_new_and_old_style_changelog) assert ctx.release_notes == expected
def test_old_style_release_notes(): """ Our old CHANGELOG template does not make the version header a link and always uses H2 headers. """ expected = """03-13-2019 16:30 PDT ### Bug Fixes - fix: throw on invalid credentials ([#281](https://github.com/googleapis/nodejs-dialogflow/pull/281))""" ctx = TagContext(release_version="v0.8.2") _get_latest_release_notes(ctx, fixture_old_style_changelog) assert ctx.release_notes == expected
def get_release_notes(ctx: TagContext) -> None: click.secho("> Grabbing the release notes.") if "google-cloud-python" in ctx.upstream_repo: changelog_path = f"{ctx.package_name}/CHANGELOG.md" else: changelog_path = "CHANGELOG.md" changelog = ctx.github.get_contents( ctx.upstream_repo, changelog_path, ref=ctx.release_pr["merge_commit_sha"]).decode("utf-8") match = re.search( r"#{2,3}[\s\W]+?" + ctx.release_version + r"*?\n(?P<notes>.+?)(\Z|\n#{2,3}\W*?\d)", changelog, re.DOTALL | re.MULTILINE, ) if match is not None: ctx.release_notes = match.group("notes").strip() else: ctx.release_notes = ""
def tag(ctx: TagContext = None) -> TagContext: if not ctx: ctx = TagContext() if ctx.interactive: click.secho(f"o/ Hey, {getpass.getuser()}, let's tag a release!", fg="magenta") if ctx.github is None: releasetool.commands.common.setup_github_context(ctx) # delegate releaase tagging to release-please default_branch = ctx.release_pr["base"]["ref"] repo = ctx.release_pr["base"]["repo"]["full_name"] with tempfile.NamedTemporaryFile("w+t", delete=False) as fp: fp.write(ctx.token) token_file = fp.name output = subprocess.check_output( [ "npx", "release-please", "github-release", f"--token={token_file}", f"--default-branch={default_branch}", "--release-type=java-yoshi", "--bump-minor-pre-major=true", f"--repo-url={repo}", "--package-name=", "--debug", ] ) ctx.release_tag = _parse_release_tag(output.decode("utf-8")) ctx.kokoro_job_name = kokoro_job_name(ctx.upstream_repo, ctx.package_name) if ctx.interactive: click.secho("\\o/ All done!", fg="magenta") return ctx
def tag(ctx: TagContext = None) -> TagContext: if not ctx: ctx = TagContext() if ctx.interactive: click.secho(f"o/ Hey, {getpass.getuser()}, let's tag a release!", fg="magenta") if ctx.github is None: releasetool.commands.common.setup_github_context(ctx) if ctx.release_pr is None: determine_release_pr(ctx) determine_release_tag(ctx) determine_package_name_and_version(ctx) # If the release already exists, don't do anything if releasetool.commands.common.release_exists(ctx): click.secho(f"{ctx.release_tag} already exists.", fg="magenta") return ctx get_release_notes(ctx) create_release(ctx) # Only enable release job triggering on google-auth-library-java for now. if ctx.upstream_repo == "googleapis/google-auth-library-java": ctx.kokoro_job_name = ( "cloud-devrel/client-libraries/java/google-auth-library-java/release/stage" ) # TODO: Move to create_release once autorelease is enabled for all # java repositories ctx.github.update_pull_labels( ctx.release_pr, add=["autorelease: tagged"], remove=["autorelease: pending"] ) releasetool.commands.common.publish_via_kokoro(ctx) if ctx.interactive: click.secho(f"\\o/ All done!", fg="magenta") return ctx
def run_releasetool_tag(lang: str, gh: github.GitHub, pull: dict) -> TagContext: """Runs releasetool tag using external config.""" language_module = importlib.import_module(f"releasetool.commands.tag.{lang}") ctx = TagContext() ctx.interactive = False # TODO(busunkim): Use proxy once KMS setup is complete. ctx.github = releasetool.github.GitHub(gh.token, use_proxy=False) ctx.token = gh.token ctx.upstream_repo = pull["base"]["repo"]["full_name"] ctx.release_pr = pull return language_module.tag(ctx)
def create_releases(ctx: TagContext) -> None: click.secho("> Creating the release.") commitish = ctx.release_pr["merge_commit_sha"] lines = ctx.release_pr["body"].splitlines() pr_comment = "" for line in lines: match = re.search(RELEASE_LINE_PATTERN, line) if match is not None: package = match.group(1) version = match.group(2) tag = package + "-" + version ctx.github.create_release( repository=ctx.upstream_repo, tag_name=tag, target_commitish=commitish, name=tag, body=f"Package {package} version {version}", # Versions like "1.0.0-beta01" or "0.9.0" are prerelease prerelease="-" in version or version.startswith("0."), ) click.secho(f"Created release for {tag}") pr_comment = pr_comment + f"- Created release for {tag}\n" if pr_comment == "": raise ValueError("No releases found within pull request") ctx.github.create_pull_request_comment(ctx.upstream_repo, ctx.release_pr["number"], pr_comment) # This isn't a tag, but that's okay - it just needs to be a commitish for # Kokoro to build against. ctx.release_tag = commitish ctx.kokoro_job_name = f"cloud-sharp/google-cloud-dotnet/gcp_windows/autorelease" ctx.github.update_pull_labels(ctx.release_pr, add=["autorelease: tagged"], remove=["autorelease: pending"]) releasetool.commands.common.publish_via_kokoro(ctx)
def determine_release_tag(ctx: TagContext) -> None: click.secho("> Determining what the release tag should be.", fg="cyan") head_ref = ctx.release_pr["head"]["ref"] # try release-please v13 pull requests title = ctx.release_pr["title"] match = re.match("chore\\(.*\\): release (\\d+\\.\\d+\\.\\d+.*)", title) if match is not None: ctx.release_tag = f"v{match.group(1)}" return match = re.match("release-(.+)", head_ref) if match is not None: ctx.release_tag = match.group(1) else: print( "I couldn't determine what the release tag should be from the PR's" f"head ref {head_ref}.") ctx.release_tag = click.prompt( "What should the release tag be (for example, v1.2.3)?") click.secho(f"Release tag is {ctx.release_tag}.")
def determine_release_pr(ctx: TagContext) -> None: click.secho( "> Let's figure out which pull request corresponds to your release.", fg="cyan" ) pulls = ctx.github.list_pull_requests(ctx.upstream_repo, state="closed") pulls = [pull for pull in pulls if "release" in pull["title"].lower()][:30] click.secho("> Please pick one of the following PRs:\n") for n, pull in enumerate(pulls, 1): print(f"\t{n}: {pull['title']} ({pull['number']})") pull_idx = click.prompt("\nWhich one do you want to tag and release?", type=int) ctx.release_pr = pulls[pull_idx - 1]
def determine_release_tag(ctx: TagContext) -> None: click.secho("> Determining the release tag.", fg="cyan") head_ref = ctx.release_pr["head"]["ref"] click.secho(f"PR head ref is {head_ref}") match = re.match(r"release-(.+)-v(\d+\.\d+\.\d+)", head_ref) if match is not None: ctx.package_name = match.group(1) ctx.release_version = match.group(2) ctx.release_tag = f"{ctx.package_name}/v{ctx.release_version}" else: click.secho( "I couldn't determine what the release tag should be from the PR's" f"head ref {head_ref}.", fg="red", ) ctx.release_tag = click.prompt( "What should the release tag be (for example, google-cloud-storage/v1.2.3)?" ) click.secho(f"Package name is {ctx.package_name}") click.secho(f"Package version is {ctx.release_version}") click.secho(f"Release tag is {ctx.release_tag}")
def tag(ctx: TagContext = None) -> TagContext: if not ctx: ctx = TagContext() if ctx.interactive: click.secho(f"o/ Hey, {getpass.getuser()}, let's tag a release!", fg="magenta") if ctx.github is None: releasetool.commands.common.setup_github_context(ctx) if ctx.release_pr is None: determine_release_pr(ctx) # If using manifest release, the manifest releaser determines # release tag, version, and release notes: if ctx.upstream_repo not in manifest_release: determine_release_tag(ctx) determine_package_version(ctx) # If the release already exists, don't do anything if releasetool.commands.common.release_exists(ctx): click.secho(f"{ctx.release_tag} already exists.", fg="magenta") return ctx get_release_notes(ctx) else: # If using mono-release strategy, fallback to using sha from # time of merge: ctx.release_tag = ctx.release_pr["merge_commit_sha"] create_release(ctx) ctx.kokoro_job_name = kokoro_job_name(ctx.upstream_repo, ctx.package_name) releasetool.commands.common.publish_via_kokoro(ctx) if ctx.interactive: click.secho("\\o/ All done!", fg="magenta") return ctx
def tag(ctx: TagContext = None) -> TagContext: if not ctx: ctx = TagContext() if ctx.interactive: click.secho(f"o/ Hey, {getpass.getuser()}, let's tag a release!", fg="magenta") if ctx.github is None: releasetool.commands.common.setup_github_context(ctx) if ctx.release_pr is None: determine_release_pr(ctx) determine_release_tag(ctx) determine_package_name_and_version(ctx) # If the release already exists, don't do anything if releasetool.commands.common.release_exists(ctx): click.secho(f"{ctx.release_tag} already exists.", fg="magenta") return ctx get_release_notes(ctx) create_release(ctx) if "google-cloud-python" in ctx.upstream_repo: ctx.kokoro_job_name = f"cloud-devrel/client-libraries/google-cloud-python/release/{ctx.package_name}" else: ctx.kokoro_job_name = ( f"cloud-devrel/client-libraries/python/{ctx.upstream_repo}/release/release" ) releasetool.commands.common.publish_via_kokoro(ctx) if ctx.interactive: click.secho(f"\\o/ All done!", fg="magenta") return ctx
def test_extracts_appropriate_release_notes_when_prior_release_is_patch(): """ see: https://github.com/googleapis/release-please/issues/140 """ ctx = TagContext(release_version="v5.0.0") _get_latest_release_notes(ctx, fixture_new_style_patch) expected = """### ⚠ BREAKING CHANGES * temp directory now defaults to setting for report directory ### Features * default temp directory to report directory ([#102](https://www.github.com/bcoe/c8/issues/102)) ([8602f4a](https://www.github.com/bcoe/c8/commit/8602f4a)) * load .nycrc/.nycrc.json to simplify migration ([#100](https://www.github.com/bcoe/c8/issues/100)) ([bd7484f](https://www.github.com/bcoe/c8/commit/bd7484f))""" assert ctx.release_notes == expected
def tag(ctx: TagContext = None) -> TagContext: if not ctx: ctx = TagContext() if ctx.interactive: click.secho(f"o/ Hey, {getpass.getuser()}, let's tag a release!", fg="magenta") if ctx.github is None: releasetool.commands.common.setup_github_context(ctx) if ctx.release_pr is None: determine_release_pr(ctx) create_releases(ctx) return ctx
def tag() -> None: ctx = TagContext() click.secho(f"o/ Hey, {getpass.getuser()}, let's tag a release!", fg="magenta") releasetool.commands.common.setup_github_context(ctx) determine_release_pr(ctx) determine_release_tag(ctx) determine_package_name_and_version(ctx) get_release_notes(ctx) create_release(ctx) wait_on_circle(ctx) click.secho(f"\\o/ All done!", fg="magenta")
def create_release(ctx: TagContext) -> None: click.secho("> Creating the release.") ctx.github_release = ctx.github.create_release( repository=ctx.upstream_repo, tag_name=ctx.release_tag, target_commitish=ctx.release_pr["merge_commit_sha"], name=ctx.release_tag, body=ctx.release_notes, ) release_location_string = f"Release is at {ctx.github_release['html_url']}" click.secho(release_location_string) ctx.github.create_pull_request_comment( ctx.upstream_repo, ctx.release_pr["number"], release_location_string )
def test_new_style_release_notes_breaking(): """ in the conventional-commits template, features/breaking-changes use an H2 header. """ expected = """### Features * added the most amazing feature ever ([42f90e2](https://www.github.com/bcoe/examples-conventional-commits/commit/42f90e2)) * adds a fancy new feature ([c46bfa3](https://www.github.com/bcoe/examples-conventional-commits/commit/c46bfa3)) ### BREAKING CHANGES * this fancy new feature breaks things * disclaimer breaks everything""" ctx = TagContext(release_version="v2.0.0") _get_latest_release_notes(ctx, fixture_new_style_changelog) assert ctx.release_notes == expected
def create_release(ctx: TagContext) -> None: click.secho("> Creating the release.") if ctx.upstream_repo in manifest_release: # delegate releaase tagging to release-please default_branch = ctx.release_pr["base"]["ref"] repo = ctx.release_pr["base"]["repo"]["full_name"] with tempfile.NamedTemporaryFile("w+t", delete=False) as fp: fp.write(ctx.token) token_file = fp.name subprocess.check_output([ # TODO(sofisl): remove pinning to a specific version # once we've tested: "npx", "release-please", "manifest-release", f"--token={token_file}", f"--default-branch={default_branch}", f"--repo-url={repo}", "--debug", ]) else: # TODO(sofisl): move the non-manifest release to release-please too # for consistency: ctx.github_release = ctx.github.create_release( repository=ctx.upstream_repo, tag_name=ctx.release_version, target_commitish=ctx.release_pr["merge_commit_sha"], name=f"{ctx.release_version}", body=ctx.release_notes, ) release_location_string = f"Release is at {ctx.github_release['html_url']}" click.secho(release_location_string) click.secho("CI will handle publishing the package to npm.") ctx.github.create_pull_request_comment(ctx.upstream_repo, ctx.release_pr["number"], release_location_string) ctx.github.update_pull_labels(ctx.release_pr, add=["autorelease: tagged"], remove=["autorelease: pending"])
def create_release(ctx: TagContext) -> None: click.secho("> Creating the release.") ctx.github_release = ctx.github.create_release( repository=ctx.upstream_repo, tag_name=ctx.release_tag, target_committish=ctx.release_pr["merge_commit_sha"], name=f"google-cloud-{ctx.package_name} {ctx.release_version}", body=ctx.release_notes, ) release_location_string = f"Release is at {ctx.github_release['html_url']}" click.secho(release_location_string) click.secho("CI will handle publishing the package to PyPI.") ctx.github.create_pull_request_comment( ctx.upstream_repo, ctx.release_pr["number"], release_location_string )
def create_release(ctx: TagContext) -> None: click.secho("> Creating the release.") ctx.github_release = ctx.github.create_release( repository=ctx.upstream_repo, tag_name=ctx.release_tag, target_commitish=ctx.release_pr["merge_commit_sha"], name=f"{ctx.package_name} {ctx.release_version}", body=ctx.release_notes, ) release_location_string = f"Release is at {ctx.github_release['html_url']}" click.secho(release_location_string) ctx.github.create_pull_request_comment(ctx.upstream_repo, ctx.release_pr["number"], release_location_string) ctx.github.update_pull_labels(ctx.release_pr, add=["autorelease: tagged"], remove=["autorelease: pending"])
def determine_package_version(ctx: TagContext) -> None: click.secho("> Determining the package version.", fg="cyan") match = re.match(r"(?P<version>v?\d+\.\d+\.\d+)", ctx.release_tag) ctx.release_version = match.group("version") click.secho(f"package version: {ctx.release_version}.")
def determine_package_name_and_version(ctx: TagContext) -> None: click.secho("> Determining the release version.", fg="cyan") match = re.match(r"v(\d+\.\d+\.\d+)", ctx.release_tag) ctx.release_version = match.group(1) click.secho(f"Package version: {ctx.release_version}.")
def determine_package_version(ctx: TagContext) -> None: click.secho("> Determining the package version.", fg="cyan") # strip the leading 'v' from the tag ctx.release_version = re.sub(r"^v", "", ctx.release_tag) click.secho(f"Package version: {ctx.release_version}.")
def get_release_notes(ctx: TagContext) -> None: click.secho("> Grabbing the release notes.", fg="cyan") ctx.release_notes = _parse_release_notes(ctx.release_pr["body"]) click.secho(f"Release notes:\n{ctx.release_notes}")
def determine_kokoro_job_name(ctx: TagContext) -> None: ctx.kokoro_job_name = ( f"cloud-devrel/client-libraries/nodejs/release/{ctx.upstream_repo}/publish" )