Skip to content

Commit

Permalink
Add: Add --repository argument for release create and sign CLI
Browse files Browse the repository at this point in the history
GitHub always uses the owner/name combination for identifying a
repository. Therefore we should allow passing this as input for creating
and signing releases.
  • Loading branch information
bjoernricks committed Feb 5, 2024
1 parent f01709f commit 2ed58cb
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 19 deletions.
25 changes: 22 additions & 3 deletions pontos/release/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
find_signing_key,
get_git_repository_name,
get_next_release_version,
repository_split,
)


Expand Down Expand Up @@ -62,6 +63,7 @@ class CreateReleaseReturnValue(IntEnum):
CREATE_RELEASE_ERROR = auto()
UPDATE_VERSION_ERROR = auto()
UPDATE_VERSION_AFTER_RELEASE_ERROR = auto()
INVALID_REPOSITORY = auto()


class CreateReleaseCommand(AsyncCommand):
Expand All @@ -87,7 +89,7 @@ def _create_changelog(
cc_config: Optional[Path],
) -> str:
changelog_builder = ChangelogBuilder(
space=self.space,
space=self.space, # type: ignore[arg-type]
project=self.project,
config=cc_config,
git_tag_prefix=self.git_tag_prefix,
Expand Down Expand Up @@ -126,7 +128,8 @@ async def async_run( # type: ignore[override]
self,
*,
token: str,
space: str,
repository: Optional[str],
space: Optional[str],
project_name: Optional[str],
versioning_scheme: VersioningScheme,
release_type: ReleaseType,
Expand All @@ -146,6 +149,8 @@ async def async_run( # type: ignore[override]
Args:
token: A token for creating a release on GitHub
repository: GitHub repository (owner/name). Overrides space and
project.
space: GitHub username or organization. Required for generating
links in the changelog.
project: Name of the project to release. If not set it will be
Expand Down Expand Up @@ -182,12 +187,25 @@ async def async_run( # type: ignore[override]
else find_signing_key(self.terminal)
)
self.git_tag_prefix = git_tag_prefix or ""

if repository:
if space:
self.print_warning(
f"Repository {repository} overrides space setting {space}"
)

try:
space, project_name = repository_split(repository)
except ValueError as e:
self.print_error(str(e))
return CreateReleaseReturnValue.INVALID_REPOSITORY

self.space = space
self.project = (
project_name
if project_name is not None
else get_git_repository_name()
)
self.space = space

self.terminal.info(f"Using versioning scheme {versioning_scheme.name}")

Expand Down Expand Up @@ -390,6 +408,7 @@ def create_release(
terminal=terminal, error_terminal=error_terminal
).run(
token=token,
repository=args.repository,
space=args.space,
project_name=args.project,
versioning_scheme=args.versioning_scheme,
Expand Down
43 changes: 32 additions & 11 deletions pontos/release/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,27 @@ def parse_args(args) -> Tuple[Optional[str], Optional[str], Namespace]:
help="The key to sign the commits and tag for a release",
default=os.environ.get("GPG_SIGNING_KEY"),
)
create_parser.add_argument(
"--project",
help="The github project",

repo_group = create_parser.add_argument_group(
"Repository",
description="Where to publish the new release. Either a full repository"
" name or a space/project combination.",
)
create_parser.add_argument(
repo_group.add_argument(
"--repository",
help="GitHub repository name (owner/name). For example "
"octocat/Hello-World",
)
repo_group.add_argument(
"--space",
default="greenbone",
help="User/Team name in github",
help="Owner (User/Team/Organization) name at GitHub",
)
repo_group.add_argument(
"--project",
help="The GitHub project",
)

create_parser.add_argument(
"--local",
action="store_true",
Expand Down Expand Up @@ -218,16 +230,25 @@ def parse_args(args) -> Tuple[Optional[str], Optional[str], Namespace]:
nargs="?",
help="Prefix for git tag versions. Default: %(default)s",
)
sign_parser.add_argument(
"--project",
help="The github project",
repo_group = sign_parser.add_argument_group(
"Repository",
description="Where to publish the new release. Either a full repository"
" name or a space/project combination.",
)
sign_parser.add_argument(
repo_group.add_argument(
"--repository",
help="GitHub repository name (owner/name). For example "
"octocat/Hello-World",
)
repo_group.add_argument(
"--space",
default="greenbone",
help="user/team name in github",
help="Owner (User/Team/Organization) name at GitHub",
)
repo_group.add_argument(
"--project",
help="The GitHub project",
)

sign_parser.add_argument(
"--passphrase",
help=(
Expand Down
27 changes: 22 additions & 5 deletions pontos/release/sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from pontos.version.helper import get_last_release_version
from pontos.version.schemes import VersioningScheme

from .helper import get_git_repository_name
from .helper import get_git_repository_name, repository_split


class SignReturnValue(IntEnum):
Expand All @@ -42,6 +42,7 @@ class SignReturnValue(IntEnum):
NO_RELEASE = auto()
UPLOAD_ASSET_ERROR = auto()
SIGNATURE_GENERATION_FAILED = auto()
INVALID_REPOSITORY = auto()


class SignatureError(PontosError):
Expand Down Expand Up @@ -175,12 +176,13 @@ async def async_run( # type: ignore[override]
self,
*,
token: str,
space: str,
repository: Optional[str],
space: Optional[str],
project: Optional[str],
versioning_scheme: VersioningScheme,
signing_key: str,
passphrase: str,
dry_run: Optional[bool] = False,
project: Optional[str],
git_tag_prefix: Optional[str],
release_version: Optional[Version],
release_series: Optional[str] = None,
Expand All @@ -190,13 +192,15 @@ async def async_run( # type: ignore[override]
Args:
token: A token for creating a release on GitHub
dry_run: True to not upload the signature files
repository: GitHub repository (owner/name). Overrides space and
project.
space: GitHub username or organization. Required for generating
links in the changelog.
project: Name of the project to release. If not set it will be
gathered via the git remote url.
versioning_scheme: The versioning scheme to use for version parsing
and calculation
dry_run: True to not upload the signature files
git_tag_prefix: An optional prefix to use for handling a git tag
from the release version.
release_version: Optional release version to use. If not set the
Expand All @@ -217,6 +221,18 @@ async def async_run( # type: ignore[override]

self.terminal.info(f"Using versioning scheme {versioning_scheme.name}")

if repository:
if space:
self.print_warning(
f"Repository {repository} overrides space setting {space}"
)

try:
space, project = repository_split(repository)
except ValueError as e:
self.print_error(str(e))
return SignReturnValue.INVALID_REPOSITORY

try:
project = (
project if project is not None else get_git_repository_name()
Expand Down Expand Up @@ -373,12 +389,13 @@ def sign(
*,
terminal: Terminal,
error_terminal: Terminal,
token: str,
token: Optional[str],
**_kwargs,
) -> SupportsInt:
return SignCommand(terminal=terminal, error_terminal=error_terminal).run(
token=token,
dry_run=args.dry_run,
repository=args.repository,
project=args.project,
space=args.space,
versioning_scheme=args.versioning_scheme,
Expand Down
147 changes: 147 additions & 0 deletions tests/release/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,112 @@ def test_release_version(

self.assertEqual(released, CreateReleaseReturnValue.SUCCESS)

@patch("pontos.release.create.Git", autospec=True)
@patch("pontos.release.create.get_last_release_version", autospec=True)
@patch(
"pontos.release.create.CreateReleaseCommand._create_release",
autospec=True,
)
@patch(
"pontos.release.create.CreateReleaseCommand._create_changelog",
autospec=True,
)
@patch("pontos.release.create.Project._gather_commands", autospec=True)
def test_release_with_repository(
self,
gather_commands_mock: MagicMock,
create_changelog_mock: MagicMock,
create_release_mock: AsyncMock,
get_last_release_version_mock: MagicMock,
git_mock: MagicMock,
):
current_version = PEP440Version("0.0.1")
release_version = PEP440Version("0.0.2")
next_version = PEP440Version("1.0.0.dev1")
command_mock = MagicMock(spec=GoVersionCommand)
gather_commands_mock.return_value = [command_mock]
create_changelog_mock.return_value = "A Changelog"
get_last_release_version_mock.return_value = current_version
command_mock.update_version.side_effect = [
VersionUpdate(
previous=current_version,
new=release_version,
changed_files=[Path("MyProject.conf")],
),
VersionUpdate(
previous=release_version,
new=next_version,
changed_files=[Path("MyProject.conf")],
),
]
git_instance_mock: MagicMock = git_mock.return_value
git_instance_mock.status.return_value = [
StatusEntry("M MyProject.conf")
]

_, token, args = parse_args(
[
"release",
"--repository",
"foo/bar",
"--release-version",
"0.0.2",
"--next-version",
"1.0.0.dev1",
]
)

with temp_git_repository():
released = create_release(
terminal=mock_terminal(),
error_terminal=mock_terminal(),
args=args,
token=token, # type: ignore[arg-type]
)

git_instance_mock.push.assert_has_calls(
[
call(follow_tags=True, remote=None),
call(follow_tags=True, remote=None),
],
)

command_mock.update_version.assert_has_calls(
[
call(release_version, force=False),
call(next_version, force=False),
]
)

self.assertEqual(
create_release_mock.await_args.args[1:], # type: ignore[union-attr]
(release_version, "foo", "A Changelog", False),
)

git_instance_mock.add.assert_has_calls(
[call(Path("MyProject.conf")), call(Path("MyProject.conf"))]
)
git_instance_mock.commit.assert_has_calls(
[
call(
"Automatic release to 0.0.2",
verify=False,
gpg_signing_key="1234",
),
call(
"Automatic adjustments after release\n\n"
"* Update to version 1.0.0.dev1\n",
verify=False,
gpg_signing_key="1234",
),
]
)
git_instance_mock.tag.assert_called_once_with(
"v0.0.2", gpg_key_id="1234", message="Automatic release to 0.0.2"
)

self.assertEqual(released, CreateReleaseReturnValue.SUCCESS)

@patch("pontos.release.create.Git", autospec=True)
@patch("pontos.release.create.get_last_release_version", autospec=True)
@patch(
Expand Down Expand Up @@ -1157,6 +1263,47 @@ def test_no_token(

self.assertEqual(released, CreateReleaseReturnValue.TOKEN_MISSING)

def test_invalid_repository(
self,
):
_, _, args = parse_args(
[
"release",
"--repository",
"foo/bar/baz",
"--release-version",
"0.0.1",
]
)

released = create_release(
terminal=mock_terminal(),
error_terminal=mock_terminal(),
args=args,
token="token",
)

self.assertEqual(released, CreateReleaseReturnValue.INVALID_REPOSITORY)

_, _, args = parse_args(
[
"release",
"--repository",
"foo_bar_baz",
"--release-version",
"0.0.1",
]
)

released = create_release(
terminal=mock_terminal(),
error_terminal=mock_terminal(),
args=args,
token="token",
)

self.assertEqual(released, CreateReleaseReturnValue.INVALID_REPOSITORY)

@patch("pontos.release.create.get_last_release_version", autospec=True)
def test_no_project_settings(
self,
Expand Down
Loading

0 comments on commit 2ed58cb

Please sign in to comment.