Skip to content

Commit

Permalink
cli: add git-create-release-commit
Browse files Browse the repository at this point in the history
* Closes #367.
  • Loading branch information
Diego Rodriguez committed Aug 7, 2020
1 parent 8bbd737 commit 2552226
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 1 deletion.
12 changes: 12 additions & 0 deletions reana/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,15 @@

TIMEOUT = 300
"""Maximum timeout to wait for results when running demo analyses in CI."""

HELM_VERSION_FILE = "Chart.yaml"
"""Helm package version file."""

OPENAPI_VERSION_FILE = "openapi.json"
"""OpenAPI version file."""

JAVASCRIPT_VERSION_FILE = "package.json"
"""JavaScript package version file."""

PYTHON_VERSION_FILE = "version.py"
"""Python package version file."""
78 changes: 77 additions & 1 deletion reana/reana_dev/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

import datetime
import os
import sys
import subprocess
import sys

import click

Expand All @@ -21,8 +21,11 @@
REPO_LIST_ALL,
REPO_LIST_SHARED,
)

from reana.reana_dev.utils import (
bump_component_version,
display_message,
get_component_current_version_from_file,
get_srcdir,
run_command,
select_components,
Expand Down Expand Up @@ -82,6 +85,45 @@ def get_current_commit(srcdir):
)


def git_is_current_version_tagged(component):
"""Determine whether the current version in source code is present as a git tag."""
tags_history = run_command(
"git tag --list", component, display=False, return_output=True
)
current_version = get_component_current_version_from_file(component)
return current_version in tags_history


def git_create_release_commit(component):
"""Create a release commit for the given component."""
if "release: v" in get_current_commit(get_srcdir(component)):
display_message("Nothing to do, last commit is a release commit.", component)
return False

if not git_is_current_version_tagged(component):
display_message(
f"Current version ({get_component_current_version_from_file(component)}) "
"not present as a git tag, plese release it and add a tag.",
component,
)
return False

next_version = bump_component_version(
component, get_component_current_version_from_file(component),
)

if (
run_command(
"git branch --show-current", component, display=False, return_output=True,
)
== "master"
):
run_command(f"git checkout -b release-{next_version}", component)

run_command(f"git commit -m 'release: {next_version}'", component)
return True


@click.group()
def git_commands():
"""Git commands group."""
Expand Down Expand Up @@ -767,4 +809,38 @@ def _commit_and_publish_version_bumps(components):
)


@click.option(
"--component",
"-c",
multiple=True,
default=["CLUSTER"],
help="Which components? [shortname|name|.|CLUSTER|ALL]",
)
@git_commands.command(name="git-create-release-commit")
def git_create_release_commit_command(component): # noqa: D301
"""Create a release commit for the specified components.
\b
:param components: The option ``component`` can be repeated. The value may
consist of:
* (1) standard component name such as
'reana-workflow-controller';
* (2) short component name such as 'r-w-controller';
* (3) special value '.' indicating component of the
current working directory;
* (4) special value 'CLUSTER' that will expand to
cover all REANA cluster components [default];
* (5) special value 'CLIENT' that will expand to
cover all REANA client components;
* (6) special value 'DEMO' that will expand
to include several runable REANA demo examples;
* (7) special value 'ALL' that will expand to include
all REANA repositories.
:type component: str
"""
for comp in select_components(component):
if git_create_release_commit(comp):
display_message("Release commit created.", comp)


git_commands_list = list(git_commands.commands.values())
153 changes: 153 additions & 0 deletions reana/reana_dev/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@
"""`reana-dev` related utils."""

import datetime
import importlib.util
import json
import os
import subprocess
import sys

import click
import semver
import yaml
from packaging.version import InvalidVersion, Version

from reana.config import (
HELM_VERSION_FILE,
JAVASCRIPT_VERSION_FILE,
OPENAPI_VERSION_FILE,
PYTHON_VERSION_FILE,
REPO_LIST_ALL,
REPO_LIST_CLIENT,
REPO_LIST_CLUSTER,
Expand Down Expand Up @@ -335,3 +344,147 @@ def get_prefixed_component_name(component):
:return: Prefixed name.
"""
return "-".join([INSTANCE_NAME, component])


def get_component_version_files(component):
"""Get a dictionary with all component's version files."""
version_files = {}
for file_ in [
HELM_VERSION_FILE,
OPENAPI_VERSION_FILE,
JAVASCRIPT_VERSION_FILE,
PYTHON_VERSION_FILE,
]:
file_relative_path = run_command(
f"git ls-files | grep -w {file_} || true",
component,
display=False,
return_output=True,
)
if file_relative_path:
version_files[file_] = os.path.join(
get_srcdir(component=component), file_relative_path
)

return version_files


def get_component_current_version_from_file(component):
"""Get component's current version."""
version_files = get_component_version_files(component)
version = ""
if version_files.get(HELM_VERSION_FILE):
with open(version_files.get(HELM_VERSION_FILE)) as f:
chart_yaml = yaml.safe_load(f.read())
version = chart_yaml["version"]

elif version_files.get(PYTHON_VERSION_FILE):
spec = importlib.util.spec_from_file_location(
component, version_files.get(PYTHON_VERSION_FILE)
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
version = module.__version__

elif version_files.get(JAVASCRIPT_VERSION_FILE):
with open(version_files.get(JAVASCRIPT_VERSION_FILE)) as f:
package_json = json.loads(f.read())
version = package_json["version"]

return version


def bump_semver2_version(current_version, part=None):
"""Bump a semver2 version string.
:param current_version: current version to be bumped
:type current_version: str
:return: String representation of the next version
:rtype: string
"""
if not semver.VersionInfo.isvalid(current_version):
click.echo(
f"Current version {current_version} is not a valid semver2 version. Please amend it"
)

parsed_current_version = semver.VersionInfo.parse(current_version)
next_version = ""
if parsed_current_version.build or part == "build":
next_version = parsed_current_version.bump_build()
elif parsed_current_version.prerelease or part == "prerelease":
next_version = parsed_current_version.next_version("prerelease")
elif parsed_current_version.patch or part == "patch":
next_version = parsed_current_version.next_version("patch")
elif parsed_current_version.minor or part == "minor":
next_version = parsed_current_version.next_version("minor")
elif parsed_current_version.major or part == "major":
next_version = parsed_current_version.next_version("major")

return str(next_version)


def bump_pep440_version(current_version, part=None):
"""Bump a PEP440 version string.
:param current_version: current version to be bumped
:param part: part of the PEP440 version to bump (one of: [major, minor, micro, pre]).
:type current_version: str
:type part: str
:return: String representation of the next version
:rtype: string
"""
try:
version = Version(current_version)
next_version = ""
if version.pre or part == "pre":
next_version = Version(
f"{version.major}.{version.minor}.{version.micro}{version.pre[0]}{version.pre[1]+1}"
)
elif version.micro or part == "micro":
next_version = Version(f"{version.major}.{version.minor}.{version.micro+1}")
elif version.minor or part == "minor":
next_version = Version(f"{version.major}.{version.minor}+1.0")
elif version.micro or part == "micro":
next_version = Version(f"{version.major}+1.0.0")

return str(next_version)
except InvalidVersion as e:
click.echo(
f"Current {current_version} is not a valid PEP440 version. Please amend it"
)


def bump_component_version(component, current_version):
"""Bump to next component version."""

def _replace_version_string(file_=None, current_version=None, next_version=None):
"""Replace the old version string with the new one in the specified file."""
run_command(
f"sed -i.bk 's/{current_version}/{next_version}/' {file_} && "
f"[ -e {file_}.bk ] && rm {file_}.bk" # Compatibility with BSD sed
)

version_files = get_component_version_files(component)
next_version = ""
files_to_update = []

if version_files.get(HELM_VERSION_FILE):
next_version = bump_semver2_version(current_version)
files_to_update.append(version_files.get(HELM_VERSION_FILE))
elif version_files.get(PYTHON_VERSION_FILE):
next_version = bump_pep440_version(current_version)
files_to_update.append(version_files.get(PYTHON_VERSION_FILE))
if version_files.get(OPENAPI_VERSION_FILE):
files_to_update.append(version_files.get(OPENAPI_VERSION_FILE))
elif version_files.get(JAVASCRIPT_VERSION_FILE):
next_version = bump_semver2_version(current_version)
files_to_update.append(version_files.get(JAVASCRIPT_VERSION_FILE))

for file_ in files_to_update:
_replace_version_string(
file_=file_, current_version=current_version, next_version=next_version,
)

return next_version
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"click>=7",
"colorama>=0.3.9",
"PyYAML>=5.1",
"semver>=2.10.2",
]


Expand Down

0 comments on commit 2552226

Please sign in to comment.