def test_minor_bump(self):
     self.assertEqual(get_new_version('0.0.0', 'minor'), '0.1.0')
     self.assertEqual(get_new_version('1.2.0', 'minor'), '1.3.0')
     self.assertEqual(get_new_version('1.2.1', 'minor'), '1.3.0')
     self.assertEqual(get_new_version('10.1.0', 'minor'), '10.2.0')
Example #2
0
async def main(draft, dry_run):
    token = os.environ["GH_TOKEN"]
    async with aiohttp.ClientSession(loop=asyncio.get_event_loop()) as session:
        gh = GitHubAPI(session, __name__, oauth_token=token)

        version_file = Path("version_number")
        current_version = version_file.read_text()

        tag_hash = str(
            git("rev-list", "-n", "1", f"v{current_version}").strip())
        print("current_version:", current_version, "[" + tag_hash[:8] + "]")

        sha = git("rev-parse", "HEAD").strip()
        print("sha:", sha)

        repo = get_repo()
        print("repo:", repo)

        commits_iter = gh.getiter(f"/repos/{repo}/commits?sha={sha}")

        commits = []

        try:
            async for item in commits_iter:
                commit_hash = item["sha"]
                commit_message = item["commit"]["message"]
                if commit_hash == tag_hash:
                    break

                try:
                    _default_parser(commit_message)
                    # if this succeeds, do nothing
                except UnknownCommitMessageStyleError as err:
                    print("Unkown commit message style:")
                    print(commit_message)
                    if sys.stdout.isatty() and click.confirm(
                            "Edit effective message?"):
                        commit_message = click.edit(commit_message)
                        _default_parser(commit_message)

                commit = Commit(commit_hash, commit_message)
                commits.append(commit)
                print("-", commit)
        except gidgethub.BadRequest:
            print(
                "BadRequest for commit retrieval. That is most likely because you forgot to push the merge commit."
            )
            return

        if len(commits) > 100:
            print(len(commits), "are a lot. Aborting!")
            sys.exit(1)

        bump = evaluate_version_bump(commits)
        print("bump:", bump)
        if bump is None:
            print("-> nothing to do")
            return
        next_version = get_new_version(current_version, bump)
        print("next version:", next_version)
        next_tag = f"v{next_version}"

        changes = generate_changelog(commits)
        md = markdown_changelog(next_version, changes, header=False)

        print(md)

        if not dry_run:
            version_file.write_text(next_version)

            git.add(version_file)
            git.commit(m=f"Bump to version {next_tag}")

            # git.tag(next_tag)
            target_hash = str(git("rev-parse", "HEAD")).strip()
            print("target_hash:", target_hash)

            git.push()

            commit_ok = False
            print("Waiting for commit", target_hash[:8], "to be received")
            for _ in range(10):
                try:
                    url = f"/repos/{repo}/commits/{target_hash}"
                    await gh.getitem(url)
                    commit_ok = True
                    break
                except InvalidField as e:
                    print("Commit", target_hash[:8], "not received yet")
                    pass  # this is what we want
                await asyncio.sleep(0.5)

            if not commit_ok:
                print("Commit", target_hash[:8], "was not created on remote")
                sys.exit(1)

            print("Commit", target_hash[:8], "received")

            await gh.post(
                f"/repos/{repo}/releases",
                data={
                    "body": md,
                    "tag_name": next_tag,
                    "name": next_tag,
                    "draft": draft,
                    "target_commitish": target_hash,
                },
            )
 def test_major_bump(self):
     self.assertEqual(get_new_version('0.0.0', 'major'), '1.0.0')
     self.assertEqual(get_new_version('0.1.0', 'major'), '1.0.0')
     self.assertEqual(get_new_version('0.1.9', 'major'), '1.0.0')
     self.assertEqual(get_new_version('10.1.0', 'major'), '11.0.0')
Example #4
0
async def pr_action(
        # token: str = typer.Argument(..., envvar="GH_TOKEN"),
):
    context = json.loads(os.environ["GITHUB_CONTEXT"])
    repo = context["repository"]
    target_branch = context["event"]["pull_request"]["base"]["ref"]
    print("Target branch:", target_branch)
    sha = context["event"]["pull_request"]["head"]["sha"]
    print("Source hash:", sha)

    token = os.environ.get("GH_TOKEN", context["token"])
    print(token)

    async with aiohttp.ClientSession(loop=asyncio.get_event_loop()) as session:
        gh = GitHubAPI(session, __name__, oauth_token=token)

        merge_commit_sha = await get_merge_commit_sha(
            context["event"]["pull_request"]["number"], repo, gh)
        print("Merge commit sha:", merge_commit_sha)

        # Get current version from target branch
        current_version = await get_release_branch_version(
            repo, target_branch, gh)
        tag_hash = await get_tag_hash(f"v{current_version}", repo, gh)
        print("current_version:", current_version, "[" + tag_hash[:8] + "]")

        commits, unparsed_commits = await get_parsed_commit_range(
            start=merge_commit_sha, end=tag_hash, repo=repo, gh=gh)

        bump = evaluate_version_bump(commits)
        print("bump:", bump)
        if bump is None:
            print("-> nothing to do")
            return
        next_version = get_new_version(current_version, bump)
        print("next version:", next_version)
        next_tag = f"v{next_version}"

        changes = generate_changelog(commits)
        md = markdown_changelog(next_version, changes, header=False)

        body = ""

        existing_release = await get_release(next_version, repo, gh)

        body += f"# `v{current_version}` -> `v{next_version}`"

        if existing_release is not None:
            if current_version == next_version:
                body += "## :no_entry_sign: Merging this will not result in a new version (no `fix`, `feat` or breaking changes). I recommend **delaying** this PR until more changes accumulate."

            else:
                body += f"## :warning: **WARNING: A release for {next_version} already exists [here]({existing_release['html_url']})** :warning:"
                body += "\n"
                body += ":no_entry_sign: I recommend to **NOT** merge this and double check the target branch!"

        if len(unparsed_commits) > 0:
            body += "\n" * 3
            body += "## :warning: This PR contains commits which are not parseable:"
            for commit in unparsed_commits:
                body += f"\n - {commit.message} ({commit.sha})"
            body += "\n **Make sure these commits do not contain changes which affect the bump version!**"

        body += "\n\n"

        body += "\n"

        body += f"## Merging this PR will create a new release `v{next_version}`\n"

        body += "### Changelog"

        body += md

        print(body)

        title = f"Release: {current_version} -> {next_version}"

        await gh.post(context["event"]["pull_request"]["url"],
                      data={
                          "body": body,
                          "title": title
                      })
Example #5
0
def assert_releaseable_changes_detected(current_tagged_version):
    bump_string = evaluate_version_bump(current_tagged_version)
    assert bump_string, "semantic-release would not create a release!"
    return bump_string


def commit_and_tag_pyproject_toml(bump_string, old_version, new_version):
    tag_message = f"Creating {bump_string} release. Bumping version number from {old_version} to {new_version}."
    commit_message = f"build(version): {tag_message}"
    tag_name = f"v{new_version}"
    repository = git.Repo(PROJECT_ROOT_PATH)
    repository.index.add([PYPROJECT_TOML_PATH])
    repository.index.commit(commit_message)
    repository.create_tag(tag_name, tag_message)


assert_clean_main()
pyproject_toml = get_current_project_version()
current_project_version = pyproject_toml["tool"]["poetry"]["version"]
assert_current_project_version_semver(current_project_version)
enable_semantic_release_logging()
current_tagged_version = get_current_version_by_tag()
bump_string = assert_releaseable_changes_detected(current_tagged_version)
new_version = get_new_version(current_tagged_version, bump_string)
print(
    f"Creating {bump_string} release! Bumping from version {current_tagged_version} to version {new_version}!"
)
pyproject_toml["tool"]["poetry"]["version"] = new_version
set_current_project_version(pyproject_toml)
commit_and_tag_pyproject_toml(bump_string, current_tagged_version, new_version)
 def test_major_bump(self):
     assert get_new_version("0.0.0", "major") == "1.0.0"
     assert get_new_version("0.1.0", "major") == "1.0.0"
     assert get_new_version("0.1.9", "major") == "1.0.0"
     assert get_new_version("10.1.0", "major") == "11.0.0"
 def test_patch_bump(self):
     assert get_new_version("0.0.0", "patch") == "0.0.1"
     assert get_new_version("0.1.0", "patch") == "0.1.1"
     assert get_new_version("10.0.9", "patch") == "10.0.10"
 def test_minor_bump(self):
     self.assertEqual(get_new_version("0.0.0", "minor"), "0.1.0")
     self.assertEqual(get_new_version("1.2.0", "minor"), "1.3.0")
     self.assertEqual(get_new_version("1.2.1", "minor"), "1.3.0")
     self.assertEqual(get_new_version("10.1.0", "minor"), "10.2.0")
 def test_patch_bump(self):
     self.assertEqual(get_new_version("0.0.0", "patch"), "0.0.1")
     self.assertEqual(get_new_version("0.1.0", "patch"), "0.1.1")
     self.assertEqual(get_new_version("10.0.9", "patch"), "10.0.10")
 def test_None_bump(self):
     self.assertEqual(get_new_version('1.0.0', None), '1.0.0')
 def test_major_bump(self):
     self.assertEqual(get_new_version("0.0.0", "major"), "1.0.0")
     self.assertEqual(get_new_version("0.1.0", "major"), "1.0.0")
     self.assertEqual(get_new_version("0.1.9", "major"), "1.0.0")
     self.assertEqual(get_new_version("10.1.0", "major"), "11.0.0")
 def test_patch_bump(self):
     self.assertEqual(get_new_version('0.0.0', 'patch'), '0.0.1')
     self.assertEqual(get_new_version('0.1.0', 'patch'), '0.1.1')
     self.assertEqual(get_new_version('10.0.9', 'patch'), '10.0.10')
 def test_minor_bump(self):
     self.assertEqual(get_new_version('0.0.0', 'minor'), '0.1.0')
     self.assertEqual(get_new_version('1.2.0', 'minor'), '1.3.0')
     self.assertEqual(get_new_version('1.2.1', 'minor'), '1.3.0')
     self.assertEqual(get_new_version('10.1.0', 'minor'), '10.2.0')
 def test_major_bump(self):
     self.assertEqual(get_new_version('0.0.0', 'major'), '1.0.0')
     self.assertEqual(get_new_version('0.1.0', 'major'), '1.0.0')
     self.assertEqual(get_new_version('0.1.9', 'major'), '1.0.0')
     self.assertEqual(get_new_version('10.1.0', 'major'), '11.0.0')
 def test_patch_bump(self):
     self.assertEqual(get_new_version('0.0.0', 'patch'), '0.0.1')
     self.assertEqual(get_new_version('0.1.0', 'patch'), '0.1.1')
     self.assertEqual(get_new_version('10.0.9', 'patch'), '10.0.10')
 def test_none_bump(self):
     self.assertEqual(get_new_version("1.0.0", None), "1.0.0")
 def test_none_bump(self):
     self.assertEqual(get_new_version('1.0.0', None), '1.0.0')
Example #18
0
async def make_release(
    token: str = typer.Argument(..., envvar="GH_TOKEN"),
    draft: bool = True,
    dry_run: bool = False,
    edit: bool = False,
):
    async with aiohttp.ClientSession(loop=asyncio.get_event_loop()) as session:
        gh = GitHubAPI(session, __name__, oauth_token=token)

        version_file = Path("version_number")
        current_version = version_file.read_text()

        tag_hash = str(git("rev-list", "-n", "1", f"v{current_version}").strip())
        print("current_version:", current_version, "[" + tag_hash[:8] + "]")

        sha = git("rev-parse", "HEAD").strip()
        print("sha:", sha)

        repo = get_repo()
        print("repo:", repo)

        commits, _ = await get_parsed_commit_range(
            start=sha, end=tag_hash, repo=repo, gh=gh, edit=edit
        )

        bump = evaluate_version_bump(commits)
        print("bump:", bump)
        if bump is None:
            print("-> nothing to do")
            return
        next_version = get_new_version(current_version, bump)
        print("next version:", next_version)
        next_tag = f"v{next_version}"

        changes = generate_changelog(commits)
        md = markdown_changelog(next_version, changes, header=False)

        print(md)

        if not dry_run:
            version_file.write_text(next_version)
            git.add(version_file)

            zenodo_file = Path(".zenodo.json")
            update_zenodo(zenodo_file, repo, next_version)
            git.add(zenodo_file)

            citation_file = Path("CITATION.cff")
            update_citation(citation_file, next_version)
            git.add(citation_file)

            git.commit(m=f"Bump to version {next_tag}")

            target_hash = str(git("rev-parse", "HEAD")).strip()
            print("target_hash:", target_hash)

            git.push()

            commit_ok = False
            print("Waiting for commit", target_hash[:8], "to be received")
            for _ in range(RETRY_COUNT):
                try:
                    url = f"/repos/{repo}/commits/{target_hash}"
                    await gh.getitem(url)
                    commit_ok = True
                    break
                except InvalidField as e:
                    print("Commit", target_hash[:8], "not received yet")
                    pass  # this is what we want
                await asyncio.sleep(RETRY_INTERVAL)

            if not commit_ok:
                print("Commit", target_hash[:8], "was not created on remote")
                sys.exit(1)

            print("Commit", target_hash[:8], "received")

            await gh.post(
                f"/repos/{repo}/releases",
                data={
                    "body": md,
                    "tag_name": next_tag,
                    "name": next_tag,
                    "draft": draft,
                    "target_commitish": target_hash,
                },
            )
 def test_minor_bump(self):
     assert type(get_new_version("0.0.0", "minor")) is str
     assert get_new_version("0.0.0", "minor") == "0.1.0"
     assert get_new_version("1.2.0", "minor") == "1.3.0"
     assert get_new_version("1.2.1", "minor") == "1.3.0"
     assert get_new_version("10.1.0", "minor") == "10.2.0"
Example #20
0
async def pr_action(
    fail: bool = False,
    pr: int = None,
    token: Optional[str] = typer.Option(None, envvar="GH_TOKEN"),
    repo: Optional[str] = typer.Option(None, envvar="GH_REPO"),
):

    print("::group::Information")

    context = os.environ.get("GITHUB_CONTEXT")

    if context is not None:
        context = json.loads(context)
        repo = context["repository"]
        token = context["token"]
    else:
        if token is None or repo is None:
            raise ValueError("No context, need token and repo")
        if pr is None:
            raise ValueError("No context, need explicit PR to run on")

    async with aiohttp.ClientSession(loop=asyncio.get_event_loop()) as session:
        gh = GitHubAPI(session, __name__, oauth_token=token)

        if pr is not None:
            pr = await gh.getitem(f"repos/{repo}/pulls/{pr}")
        else:
            pr = context["event"]["pull_request"]

        target_branch = pr["base"]["ref"]
        print("Target branch:", target_branch)
        sha = pr["head"]["sha"]
        print("Source hash:", sha)

        merge_commit_sha = await get_merge_commit_sha(
            pr["number"],
            repo,
            gh,
        )
        print("Merge commit sha:", merge_commit_sha)

        # Get current version from target branch
        current_version = await get_release_branch_version(repo, target_branch, gh)
        tag_hash = await get_tag_hash(f"v{current_version}", repo, gh)
        print("current_version:", current_version, "[" + tag_hash[:8] + "]")

        commits, unparsed_commits = await get_parsed_commit_range(
            start=merge_commit_sha, end=tag_hash, repo=repo, gh=gh
        )

        bump = evaluate_version_bump(commits)
        print("bump:", bump)
        next_version = get_new_version(current_version, bump)
        print("next version:", next_version)
        next_tag = f"v{next_version}"

        print("::endgroup::")

        changes = generate_changelog(commits)
        md = markdown_changelog(next_version, changes, header=False)

        body = ""
        title = f"Release: {current_version} -> {next_version}"

        existing_release = await get_release(next_tag, repo, gh)
        existing_tag = await get_tag(next_tag, repo, gh)

        body += f"# `v{current_version}` -> `v{next_version}`\n"

        exit_code = 0

        if existing_release is not None or existing_tag is not None:

            if current_version == next_version:
                body += (
                    "## :no_entry_sign: Merging this will not result in a new version (no `fix`, "
                    "`feat` or breaking changes). I recommend **delaying** this PR until more changes accumulate.\n"
                )
                print("::warning::Merging this will not result in a new version")

            else:
                exit_code = 1
                title = f":no_entry_sign: {title}"
                if existing_release is not None:
                    body += f"## :warning: **WARNING**: A release for '{next_tag}' already exists"
                    body += f"[here]({existing_release['html_url']})** :warning:"
                    print(f"::error::A release for tag '{next_tag}' already exists")
                else:
                    body += (
                        f"## :warning: **WARNING**: A tag '{next_tag}' already exists"
                    )
                    print(f"::error::A tag '{next_tag}' already exists")

                body += "\n"
                body += ":no_entry_sign: I recommend to **NOT** merge this and double check the target branch!\n\n"

        else:
            body += f"## Merging this PR will create a new release `v{next_version}`\n"

        if len(unparsed_commits) > 0:
            body += "\n" * 3
            body += "## :warning: This PR contains commits which are not parseable:"
            for commit in unparsed_commits:
                msg, _ = commit.message.split("\n", 1)
                body += f"\n - {msg} {commit.sha})"
            body += "\n **Make sure these commits do not contain changes which affect the bump version!**"

        body += "\n\n"

        body += "### Changelog"

        body += md

        print("::group::PR message")
        print(body)
        print("::endgroup::")

        await gh.post(pr["url"], data={"body": body, "title": title})

        if fail:
            sys.exit(exit_code)
 def test_none_bump(self):
     assert get_new_version("1.0.0", None) == "1.0.0"
Example #22
0
    def test_prerelease(self):
        assert get_new_version("1.0.1-beta.1", "1.0.0", None,
                               True) == "1.0.1-beta.2"
        assert get_new_version("1.0.1-beta.1", "1.0.0", "major",
                               True) == "2.0.0-beta.1"
        assert get_new_version("1.0.1-beta.1", "1.0.0", "minor",
                               True) == "1.1.0-beta.1"
        assert get_new_version("1.0.1-beta.1", "1.0.0", "patch",
                               True) == "1.0.1-beta.2"

        assert get_new_version("1.0.0", "1.0.0", None, True) == "1.0.1-beta.1"
        assert get_new_version("1.0.0", "1.0.0", "major",
                               True) == "2.0.0-beta.1"
        assert get_new_version("1.0.0", "1.0.0", "minor",
                               True) == "1.1.0-beta.1"
        assert get_new_version("1.0.0", "1.0.0", "patch",
                               True) == "1.0.1-beta.1"

        assert get_new_version("0.9.0-beta.1", "1.0.0", None,
                               True) == "1.0.1-beta.1"
        assert get_new_version("0.9.0-beta.1", "1.0.0", "major",
                               True) == "2.0.0-beta.1"
        assert get_new_version("0.9.0-beta.1", "1.0.0", "minor",
                               True) == "1.1.0-beta.1"
        assert get_new_version("0.9.0-beta.1", "1.0.0", "patch",
                               True) == "1.0.1-beta.1"

        with pytest.raises(ValueError):
            get_new_version("0.9.0", "1.0.0", None, True)

        with pytest.raises(ValueError):
            get_new_version("1.0.0", "0.9.0", None, True)