-
Notifications
You must be signed in to change notification settings - Fork 236
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d2314f8
commit bbbbfeb
Showing
5 changed files
with
329 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
from semantic_release.hvcs._base import HvcsBase | ||
from semantic_release.hvcs.bitbucket import Bitbucket | ||
from semantic_release.hvcs.gitea import Gitea | ||
from semantic_release.hvcs.github import Github | ||
from semantic_release.hvcs.gitlab import Gitlab | ||
from semantic_release.hvcs.token_auth import TokenAuth | ||
|
||
__all__ = ["Gitea", "Github", "Gitlab", "HvcsBase", "TokenAuth"] | ||
__all__ = ["Bitbucket", "Gitea", "Github", "Gitlab", "HvcsBase", "TokenAuth"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
"""Helper code for interacting with a Bitbucket remote VCS""" | ||
|
||
# Note: Bitbucket doesn't support releases. But it allows users to use | ||
# `semantic-release version` without having to specify `--no-vcs-release`. | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
import mimetypes | ||
import os | ||
from functools import lru_cache | ||
|
||
from semantic_release.hvcs._base import HvcsBase | ||
from semantic_release.hvcs.token_auth import TokenAuth | ||
from semantic_release.hvcs.util import build_requests_session | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
# Add a mime type for wheels | ||
# Fix incorrect entries in the `mimetypes` registry. | ||
# On Windows, the Python standard library's `mimetypes` reads in | ||
# mappings from file extension to MIME type from the Windows | ||
# registry. Other applications can and do write incorrect values | ||
# to this registry, which causes `mimetypes.guess_type` to return | ||
# incorrect values, which causes TensorBoard to fail to render on | ||
# the frontend. | ||
# This method hard-codes the correct mappings for certain MIME | ||
# types that are known to be either used by python-semantic-release or | ||
# problematic in general. | ||
mimetypes.add_type("application/octet-stream", ".whl") | ||
mimetypes.add_type("text/markdown", ".md") | ||
|
||
|
||
class Bitbucket(HvcsBase): | ||
"""Bitbucket helper class""" | ||
|
||
API_VERSION = "2.0" | ||
DEFAULT_DOMAIN = "bitbucket.org" | ||
DEFAULT_API_DOMAIN = "api.bitbucket.org" | ||
DEFAULT_ENV_TOKEN_NAME = "BITBUCKET_TOKEN" | ||
|
||
def __init__( | ||
self, | ||
remote_url: str, | ||
hvcs_domain: str | None = None, | ||
hvcs_api_domain: str | None = None, | ||
token: str | None = None, | ||
) -> None: | ||
self._remote_url = remote_url | ||
|
||
if hvcs_domain is not None: | ||
self.hvcs_domain = hvcs_domain | ||
else: | ||
api_url = os.getenv("BITBUCKET_SERVER_URL", self.DEFAULT_DOMAIN) | ||
self.hvcs_domain = api_url.replace("https://", "") | ||
|
||
# ref: https://developer.atlassian.com/cloud/bitbucket/rest/intro/#uri-uuid | ||
if hvcs_api_domain is not None: | ||
self.hvcs_api_domain = hvcs_api_domain | ||
else: | ||
api_url = os.getenv("BITBUCKET_API_URL", self.DEFAULT_API_DOMAIN) | ||
self.hvcs_api_domain = api_url.replace("https://", "") | ||
|
||
self.api_url = f"https://{self.hvcs_api_domain}/{self.API_VERSION}" | ||
|
||
self.token = token | ||
auth = None if not self.token else TokenAuth(self.token) | ||
self.session = build_requests_session(auth=auth) | ||
|
||
@lru_cache(maxsize=1) | ||
def _get_repository_owner_and_name(self) -> tuple[str, str]: | ||
# ref: https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ | ||
if "BITBUCKET_REPO_FULL_NAME" in os.environ: | ||
log.info("Getting repository owner and name from environment variables.") | ||
owner, name = os.environ["BITBUCKET_REPO_FULL_NAME"].rsplit("/", 1) | ||
return owner, name | ||
return super()._get_repository_owner_and_name() | ||
|
||
def compare_url(self, from_rev: str, to_rev: str) -> str: | ||
""" | ||
Get the Bitbucket comparison link between two version tags. | ||
:param from_rev: The older version to compare. | ||
:param to_rev: The newer version to compare. | ||
:return: Link to view a comparison between the two versions. | ||
""" | ||
return ( | ||
f"https://{self.hvcs_domain}/{self.owner}/{self.repo_name}/" | ||
f"branches/compare/{from_rev}%0D{to_rev}" | ||
) | ||
|
||
def remote_url(self, use_token: bool = True) -> str: | ||
if not use_token: | ||
# Note: Assume the user is using SSH. | ||
return self._remote_url | ||
if not self.token: | ||
raise ValueError("Requested to use token but no token set.") | ||
user = os.environ.get("BITBUCKET_USER") | ||
if user: | ||
# Note: If the user is set, assume the token is an app secret. This will work | ||
# on any repository the user has access to. | ||
# https://support.atlassian.com/bitbucket-cloud/docs/push-back-to-your-repository | ||
return ( | ||
f"https://{user}:{self.token}@" | ||
f"{self.hvcs_domain}/{self.owner}/{self.repo_name}.git" | ||
) | ||
else: | ||
# Note: Assume the token is a repository token which will only work on the | ||
# repository it was created for. | ||
# https://support.atlassian.com/bitbucket-cloud/docs/using-access-tokens | ||
return ( | ||
f"https://x-token-auth:{self.token}@" | ||
f"{self.hvcs_domain}/{self.owner}/{self.repo_name}.git" | ||
) | ||
|
||
def commit_hash_url(self, commit_hash: str) -> str: | ||
return ( | ||
f"https://{self.hvcs_domain}/{self.owner}/{self.repo_name}/" | ||
f"commits/{commit_hash}" | ||
) | ||
|
||
def pull_request_url(self, pr_number: str | int) -> str: | ||
return ( | ||
f"https://{self.hvcs_domain}/{self.owner}/{self.repo_name}/" | ||
f"pull-requests/{pr_number}" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
import os | ||
from unittest import mock | ||
|
||
import pytest | ||
from requests import Session | ||
|
||
from semantic_release.hvcs.bitbucket import Bitbucket | ||
from tests.const import EXAMPLE_REPO_NAME, EXAMPLE_REPO_OWNER | ||
|
||
|
||
@pytest.fixture | ||
def default_bitbucket_client(): | ||
remote_url = f"git@bitbucket.org:{EXAMPLE_REPO_OWNER}/{EXAMPLE_REPO_NAME}.git" | ||
return Bitbucket(remote_url=remote_url) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
( | ||
"patched_os_environ, hvcs_domain, hvcs_api_domain, " | ||
"expected_hvcs_domain, expected_hvcs_api_domain" | ||
), | ||
[ | ||
({}, None, None, Bitbucket.DEFAULT_DOMAIN, Bitbucket.DEFAULT_API_DOMAIN), | ||
( | ||
{"BITBUCKET_SERVER_URL": "https://special.custom.server/vcs/"}, | ||
None, | ||
None, | ||
"special.custom.server/vcs/", | ||
Bitbucket.DEFAULT_API_DOMAIN, | ||
), | ||
( | ||
{"BITBUCKET_API_URL": "https://api.special.custom.server/"}, | ||
None, | ||
None, | ||
Bitbucket.DEFAULT_DOMAIN, | ||
"api.special.custom.server/", | ||
), | ||
( | ||
{"BITBUCKET_SERVER_URL": "https://special.custom.server/vcs/"}, | ||
"https://example.com", | ||
None, | ||
"https://example.com", | ||
Bitbucket.DEFAULT_API_DOMAIN, | ||
), | ||
( | ||
{"BITBUCKET_API_URL": "https://api.special.custom.server/"}, | ||
None, | ||
"https://api.example.com", | ||
Bitbucket.DEFAULT_DOMAIN, | ||
"https://api.example.com", | ||
), | ||
], | ||
) | ||
@pytest.mark.parametrize( | ||
"remote_url", | ||
[ | ||
f"git@bitbucket.org:{EXAMPLE_REPO_OWNER}/{EXAMPLE_REPO_NAME}.git", | ||
f"https://bitbucket.org/{EXAMPLE_REPO_OWNER}/{EXAMPLE_REPO_NAME}.git", | ||
], | ||
) | ||
@pytest.mark.parametrize("token", ("abc123", None)) | ||
def test_bitbucket_client_init( | ||
patched_os_environ, | ||
hvcs_domain, | ||
hvcs_api_domain, | ||
expected_hvcs_domain, | ||
expected_hvcs_api_domain, | ||
remote_url, | ||
token, | ||
): | ||
with mock.patch.dict(os.environ, patched_os_environ, clear=True): | ||
client = Bitbucket( | ||
remote_url=remote_url, | ||
hvcs_domain=hvcs_domain, | ||
hvcs_api_domain=hvcs_api_domain, | ||
token=token, | ||
) | ||
|
||
assert client.hvcs_domain == expected_hvcs_domain | ||
assert client.hvcs_api_domain == expected_hvcs_api_domain | ||
assert client.api_url == f"https://{client.hvcs_api_domain}/2.0" | ||
assert client.token == token | ||
assert client._remote_url == remote_url | ||
assert hasattr(client, "session") | ||
assert isinstance(getattr(client, "session", None), Session) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"patched_os_environ, expected_owner, expected_name", | ||
[ | ||
({}, None, None), | ||
({"BITBUCKET_REPO_FULL_NAME": "path/to/repo/foo"}, "path/to/repo", "foo"), | ||
], | ||
) | ||
def test_bitbucket_get_repository_owner_and_name( | ||
default_bitbucket_client, patched_os_environ, expected_owner, expected_name | ||
): | ||
with mock.patch.dict(os.environ, patched_os_environ, clear=True): | ||
if expected_owner is None and expected_name is None: | ||
assert ( | ||
default_bitbucket_client._get_repository_owner_and_name() | ||
== super( | ||
Bitbucket, default_bitbucket_client | ||
)._get_repository_owner_and_name() | ||
) | ||
else: | ||
assert default_bitbucket_client._get_repository_owner_and_name() == ( | ||
expected_owner, | ||
expected_name, | ||
) | ||
|
||
|
||
def test_compare_url(default_bitbucket_client): | ||
assert default_bitbucket_client.compare_url( | ||
from_rev="revA", to_rev="revB" | ||
) == "https://{domain}/{owner}/{repo}/branches/compare/revA%0DrevB".format( | ||
domain=default_bitbucket_client.hvcs_domain, | ||
owner=default_bitbucket_client.owner, | ||
repo=default_bitbucket_client.repo_name, | ||
) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"patched_os_environ, use_token, token, _remote_url, expected", | ||
[ | ||
( | ||
{"BITBUCKET_USER": "foo"}, | ||
False, | ||
"", | ||
"git@bitbucket.org:custom/example.git", | ||
"git@bitbucket.org:custom/example.git", | ||
), | ||
( | ||
{}, | ||
False, | ||
"aabbcc", | ||
"git@bitbucket.org:custom/example.git", | ||
"git@bitbucket.org:custom/example.git", | ||
), | ||
( | ||
{}, | ||
True, | ||
"aabbcc", | ||
"git@bitbucket.org:custom/example.git", | ||
"https://x-token-auth:aabbcc@bitbucket.org/custom/example.git", | ||
), | ||
( | ||
{"BITBUCKET_USER": "foo"}, | ||
False, | ||
"aabbcc", | ||
"git@bitbucket.org:custom/example.git", | ||
"git@bitbucket.org:custom/example.git", | ||
), | ||
( | ||
{"BITBUCKET_USER": "foo"}, | ||
True, | ||
"aabbcc", | ||
"git@bitbucket.org:custom/example.git", | ||
"https://foo:aabbcc@bitbucket.org/custom/example.git", | ||
), | ||
], | ||
) | ||
def test_remote_url( | ||
patched_os_environ, | ||
use_token, | ||
token, | ||
_remote_url, | ||
expected, | ||
default_bitbucket_client, | ||
): | ||
with mock.patch.dict(os.environ, patched_os_environ, clear=True): | ||
default_bitbucket_client._remote_url = _remote_url | ||
default_bitbucket_client.token = token | ||
assert default_bitbucket_client.remote_url(use_token=use_token) == expected | ||
|
||
|
||
def test_commit_hash_url(default_bitbucket_client): | ||
sha = "244f7e11bcb1e1ce097db61594056bc2a32189a0" | ||
assert default_bitbucket_client.commit_hash_url( | ||
sha | ||
) == "https://{domain}/{owner}/{repo}/commits/{sha}".format( | ||
domain=default_bitbucket_client.hvcs_domain, | ||
owner=default_bitbucket_client.owner, | ||
repo=default_bitbucket_client.repo_name, | ||
sha=sha, | ||
) | ||
|
||
|
||
@pytest.mark.parametrize("pr_number", (420, "420")) | ||
def test_pull_request_url(default_bitbucket_client, pr_number): | ||
assert default_bitbucket_client.pull_request_url( | ||
pr_number=pr_number | ||
) == "https://{domain}/{owner}/{repo}/pull-requests/{pr_number}".format( | ||
domain=default_bitbucket_client.hvcs_domain, | ||
owner=default_bitbucket_client.owner, | ||
repo=default_bitbucket_client.repo_name, | ||
pr_number=pr_number, | ||
) |