def cluster_build(build_arg, mode, exclude_components, no_cache, skip_load): # noqa: D301 """Build REANA cluster. \b Example: $ reana-dev cluster-build --exclude-components=r-ui,r-a-vomsproxy -b COMPUTE_BACKENDS=kubernetes,htcondorcern,slurmcern --mode debug --no-cache """ cmds = [] # initalise common submodules if mode in ("latest", "debug"): cmds.append("reana-dev git-submodule --update") # build Docker images cmd = "reana-dev docker-build" if exclude_components: cmd += " --exclude-components {}".format(exclude_components) for arg in build_arg: cmd += " -b {0}".format(arg) if mode in ("debug"): cmd += " -b DEBUG=1" if no_cache: cmd += " --no-cache" cmds.append(cmd) if not skip_load and mode in ("releasepypi", "latest", "debug"): # load built Docker images into cluster cmd = "reana-dev kind-load-docker-image -c CLUSTER" if exclude_components: cmd += " --exclude-components {}".format(exclude_components) cmds.append(cmd) # execute commands for cmd in cmds: run_command(cmd, "reana")
def kubectl_delete_pod(component): # noqa: D301 """Delete REANA component's pod. If option ``component`` is not used, all pods will be deleted. \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 """ if "ALL" in component: cmd = "kubectl delete --all pods --wait=false" run_command(cmd) else: components = select_components(component) for component in components: if component in COMPONENT_PODS: cmd = "kubectl delete pod --wait=false -l app={0}".format( COMPONENT_PODS[component] ) run_command(cmd, component)
def git_clean(component): # noqa: D301 """Clean REANA source repository code tree. Removes all non-source-controlled files in the component source code repository. Useful to run before building container images. \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 """ components = select_components(component) for component in components: for cmd in [ "git clean -d -ff -x", ]: run_command(cmd, component)
def git_checkout_pr(branch, fetch): # noqa: D301 """Check out local branch corresponding to a component pull request. The ``-b`` option can be repetitive to check out several pull requests in several repositories at the same time. \b :param branch: The option ``branch`` can be repeated. The value consist of two strings specifying the component name and the pull request number. For example, ``-b reana-workflow-controler 72`` will create a local branch called ``pr-72`` in the reana-workflow-controller source code directory. :param fetch: Should we fetch latest upstream first? [default=False] :type branch: list :type fetch: bool """ for cpr in branch: component, pull_request = cpr component = select_components([ component, ])[0] if component in REPO_LIST_ALL: if fetch: cmd = "git fetch upstream" run_command(cmd, component) cmd = "git checkout -b pr-{0} upstream/pr/{0}".format(pull_request) run_command(cmd, component) else: msg = "Ignoring unknown component." display_message(msg, component)
def git_tag(component): # noqa: D301 """Create the corresponding git tag for components with release commits. \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; * (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 """ components = select_components(component) for component in components: if not is_last_commit_release_commit(component): click.secho( "The last commit is not a release commit. Please use `reana-dev git-create-release-commit`.", fg="red", ) sys.exit(1) current_version = get_current_component_version_from_source_files( component) run_command(f"git tag {current_version}")
def docker_rmi(user, tag, component): # noqa: D301 """Remove REANA component images. \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. :param user: DockerHub organisation or user name. [default=reanahub] :param tag: Docker tag to use. [default=latest] :type component: str :type user: str :type tag: str """ components = select_components(component) for component in components: if is_component_dockerised(component): cmd = "docker rmi {0}/{1}:{2}".format(user, component, tag) run_command(cmd, component) else: msg = "Ignoring this component that does not contain" " a Dockerfile." display_message(msg, component)
def git_diff(component, exclude_components, base): # noqa: D301 """Diff checked-out REANA local source code repositories. \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. :param exclude_components: List of components to exclude from command. :param base: Against which git base branch are we working on? [default=master] :type component: str :type exclude_components: str :type base: str """ if exclude_components: exclude_components = exclude_components.split(",") components = select_components(component, exclude_components) for component in components: for cmd in [ "git diff {}".format(base), ]: run_command(cmd, component)
def python_install_eggs(): """Create eggs-info/ in all REANA infrastructure and runtime components.""" for component in REPO_LIST_CLUSTER: if is_component_python_package(component): for cmd in [ "python setup.py bdist_egg", ]: run_command(cmd, component)
def docker_images(user): # noqa: D301 """List REANA component images. :param user: DockerHub user name. [default=reanahub] :type user: str """ cmd = "docker images | grep {0}".format(user) run_command(cmd)
def client_install(): # noqa: D301 """Install latest REANA client and its dependencies.""" for component in REPO_LIST_CLIENT: for cmd in [ "pip install . --upgrade", ]: run_command(cmd, component) run_command("pip check", "reana")
def _git_create_pr(comp): """Create a pull request for the provided component.""" for cmd in [ "git push origin HEAD", "hub pull-request -p --no-edit", "hub pr list -L 1", ]: run_command(cmd, component)
def git_push_to_origin(components): """Push current branch to origin.""" for component in components: branch = run_command("git branch --show-current", component, return_output=True) run_command( f"git push --force origin {branch}", component, )
def cluster_undeploy(): # noqa: D301 """Undeploy REANA cluster.""" is_deployed = run_command("helm ls", "reana", return_output=True) if "reana" in is_deployed: for cmd in [ "helm uninstall reana -n default", "kubectl get secrets -o custom-columns=':metadata.name' | grep reana | xargs kubectl delete secret", "docker exec -i -t kind-control-plane sh -c '/bin/rm -rf /var/reana/*'", ]: run_command(cmd, "reana") else: msg = "No REANA cluster to undeploy." display_message(msg, "reana")
def _create_commit_or_amend(components): for c in components: commit_cmd = 'git commit -m "installation: bump shared modules"' if amend: commit_cmd = "git commit --amend --no-edit" files_to_commit = ["setup.py"] if os.path.exists(get_srcdir(c) + os.sep + "requirements.txt"): files_to_commit.append("requirements.txt") run_command( f"git add {' '.join(files_to_commit)} && {commit_cmd}", c, )
def release_helm(ctx, user: str, dry_run: bool) -> None: # noqa: D301 """Release REANA as a Helm chart.""" component = "reana" version = get_current_component_version_from_source_files(component) is_chart_releaser_installed = which("cr") github_pages_branch = "gh-pages" package_path = ".cr-release-packages" index_path = ".cr-index" repository = f"https://{user}.github.io/{component}" is_component_releasable(component, exit_code=True, display=True) if not is_chart_releaser_installed: click.secho( "Please install chart-releaser to be able to do a Helm release", fg="red", ) sys.exit(1) if not os.getenv("CR_TOKEN"): click.secho( "Please provide your GitHub token as CR_TOKEN environment variable", fg="red", ) sys.exit(1) current_commit_sha = get_current_commit( get_srcdir(component)).split(" ")[0] for cmd in [ f"rm -rf {package_path}", f"mkdir {package_path}", f"rm -rf {index_path}", f"mkdir {index_path}", f"helm package helm/reana --destination {package_path} --dependency-update", f"cr upload -o {user} -r {component} --release-name-template '{{{{ .Version }}}}' --commit {current_commit_sha}", f"cr index -o {user} -r {component} -c {repository} --release-name-template '{{{{ .Version }}}}'", ]: run_command(cmd, component, dry_run=dry_run) with tempfile.TemporaryDirectory() as gh_pages_worktree: run_command( f"git worktree add '{gh_pages_worktree}' gh-pages && " f"cd {gh_pages_worktree} && " f"cp -f {get_srcdir(component) + os.sep + index_path}/index.yaml {gh_pages_worktree}/index.yaml && " f"git add index.yaml && " f"git commit -m 'index.yaml: {version}' && " f"git push origin {github_pages_branch} && " f"cd - && " f"git worktree remove '{gh_pages_worktree}'", dry_run=dry_run, )
def git_submodule(update=False, status=False, delete=False): # noqa: D301 """Sync REANA shared modules across all the repositories. Take currently checked-out reana-commons and reana-db modules and sync them across REANA components. Useful for building container images with not-yet-released shared modules. The option ``--update`` propagates the shared modules across the code base as necessary. Useful before running local docker image building. The option ``--status`` shows the information about shared modules. The option ``--delete`` removes the shared modules from everywhere. Useful for clean up after testing. """ if update: for component in COMPONENTS_USING_SHARED_MODULE_COMMONS: for cmd in [ "rsync -az ../reana-commons modules", ]: run_command(cmd, component) for component in COMPONENTS_USING_SHARED_MODULE_DB: for cmd in [ "rsync -az ../reana-db modules", ]: run_command(cmd, component) elif delete: for component in set(COMPONENTS_USING_SHARED_MODULE_COMMONS + COMPONENTS_USING_SHARED_MODULE_DB): for cmd in [ "rm -rf ./modules/", ]: run_command(cmd, component) elif status: for component in COMPONENTS_USING_SHARED_MODULE_COMMONS: for cmd in [ "git status -s", ]: run_command(cmd, component) for component in COMPONENTS_USING_SHARED_MODULE_DB: for cmd in [ "git status -s", ]: run_command(cmd, component) else: click.echo("Unknown action. Please specify `--update`, `--status` " " or `--delete`. Exiting.") sys.exit(1)
def is_cluster_created(): """Return True/False based on whether there is a cluster created already.""" cmd = "kind get clusters" output = run_command(cmd, "reana", return_output=True) if "kind" in output: return True return False
def print_branch_difference_report( component, branch_to_compare, current_branch=None, base=GIT_DEFAULT_BASE_BRANCH, commit=None, short=False, ): """Report to stdout the differences between two branches.""" # detect how far it is ahead/behind from pr/origin/upstream current_branch = current_branch or get_current_branch( get_srcdir(component)) commit = commit or get_current_commit(get_srcdir(component)) report = "" behind, ahead = compare_branches(branch_to_compare, current_branch) if ahead or behind: report += "(" if ahead: report += "{0} AHEAD ".format(ahead) if behind: report += "{0} BEHIND ".format(behind) report += branch_to_compare + ")" # detect rebase needs for local branches and PRs if branch_to_compare != "upstream/{}".format(base): branch_to_compare = "upstream/{}".format(base) behind, ahead = compare_branches(branch_to_compare, current_branch) if behind: report += "(STEMS FROM " if behind: report += "{0} BEHIND ".format(behind) report += branch_to_compare + ")" click.secho("{0}".format(component), nl=False, bold=True) click.secho(" @ ", nl=False, dim=True) if current_branch == base: click.secho("{0}".format(current_branch), nl=False) else: click.secho("{0}".format(current_branch), nl=False, fg="green") if report: click.secho(" {0}".format(report), nl=False, fg="red") click.secho(" @ ", nl=False, dim=True) click.secho("{0}".format(commit)) # optionally, display also short status if short: cmd = "git status --short" run_command(cmd, component, display=False)
def release_docker(ctx, component, user, image_name): # noqa: D301 """Release a component on Docker Hub. \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 """ components = select_components(component) cannot_release_on_dockerhub = [] for component_ in components: if not is_component_dockerised(component_): cannot_release_on_dockerhub.append(component_) is_component_releasable(component_, exit_code=True, display=True) full_image_name = f"{user}/{image_name or component_}" docker_tag = get_docker_tag(component_) run_command( f"docker tag {full_image_name}:latest {full_image_name}:{docker_tag}", component_, ) ctx.invoke(docker_push, component=[component_], tag=docker_tag, user=user) if cannot_release_on_dockerhub: click.secho( "The following components are not releasable on DockerHub: " f"{', '.join(cannot_release_on_dockerhub)}", fg="red", ) sys.exit(1)
def git_merge(branch, base, push): # noqa: D301 """Merge a component pull request to local base branch. The ``-b`` option can be repetitive to merge several pull requests in several repositories at the same time. \b :param branch: The option ``branch`` can be repeated. The value consist of two strings specifying the component name and the pull request number. For example, ``-b reana-workflow-controler 72`` will merge a local branch called ``pr-72`` from the reana-workflow-controller to the base branch. :param base: Against which git base branch are we working on? [default=master] :param push: Should we push to origin and upstream? [default=False] :type base: str :type branch: list :type push: bool """ for cpr in branch: component, pull_request = cpr component = select_components([ component, ])[0] if component in REPO_LIST_ALL: for cmd in [ "git fetch upstream", "git diff pr-{0}..upstream/pr/{0} --exit-code".format( pull_request), "git checkout {0}".format(base), "git merge --ff-only upstream/{0}".format(base), "git merge --ff-only upstream/pr/{0}".format(pull_request), "git branch -d pr-{0}".format(pull_request), ]: run_command(cmd, component) if push: for cmd in [ "git push origin {0}".format(base), "git push upstream {0}".format(base), ]: run_command(cmd, component) else: msg = "Ignoring unknown component." display_message(msg, component)
def release_pypi(ctx, component, timeout): # noqa: D301 """Release a component on pypi.org. \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 """ components = select_components(component) for component in components: is_component_releasable(component, exit_code=True, display=True) ctx.invoke(git_clean, component=[component]) for cmd in [ "rm -rf dist", "python setup.py sdist", "twine upload ./dist/*" ]: run_command(cmd, component) retry_interval = 15 time_elapsed = 0 while fetch_latest_pypi_version( component) != get_current_component_version_from_source_files( component): sleep(retry_interval) time_elapsed += retry_interval if time_elapsed >= timeout: click.secho("Something went wrong with the PyPI release.", fg="red") sys.exit(1) click.secho(f"{component} successfully released on PyPI", fg="green")
def kind_load_docker_image(user, component, node, exclude_components): # noqa: D301 """Load Docker images to the cluster. \b :param user: DockerHub organisation or user name. [default=reanahub] :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. :param exclude_components: List of components to exclude from the build. :type user: str :type component: str :type exclude_components: str """ if exclude_components: exclude_components = exclude_components.split(",") for component in select_components(component, exclude_components): if component in DOCKER_PREFETCH_IMAGES: for image in DOCKER_PREFETCH_IMAGES[component]: cmd = "kind load docker-image {0}".format(image) if node: cmd = f"{cmd} --nodes {','.join(node)}" run_command(cmd, component) elif is_component_dockerised(component): cmd = "kind load docker-image {0}/{1}".format(user, component) if node: cmd = f"{cmd} --nodes {','.join(node)}" run_command(cmd, component) else: msg = "Ignoring this component that does not contain" " a Dockerfile." display_message(msg, component)
def git_upgrade(component, exclude_components, base): # noqa: D301 """Upgrade REANA local source code repositories and push to GitHub origin. \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. :param exclude_components: List of components to exclude from command. :param base: Against which git base branch are we working on? [default=master] :type component: str :type exclude_components: str :type base: str """ if exclude_components: exclude_components = exclude_components.split(",") components = select_components(component, exclude_components) for component in components: if not branch_exists(component, base): display_message("Missing branch {}, skipping.".format(base), component=component) continue for cmd in [ "git fetch upstream", "git checkout {0}".format(base), "git merge --ff-only upstream/{0}".format(base), "git push origin {0}".format(base), "git checkout -", ]: run_command(cmd, component)
def git_is_current_version_tagged(component): """Determine whether the current version in source code is present as a git tag.""" current_version = get_current_component_version_from_source_files( component) is_version_tagged = int( run_command( f"git tag --list {current_version} | wc -l", component, display=False, return_output=True, )) return bool(is_version_tagged)
def cluster_delete(mounts): # noqa: D301 """Delete REANA cluster. \b Example: $ reana-dev cluster-delete -m /var/reana:/var/reana """ cmds = [] # delete cluster cmds.append("kind delete cluster") # remove only local paths where cluster path starts with /var/reana for safety for mount in mounts: local_path, cluster_node_path = mount.split(":") if cluster_node_path.startswith("/var/reana"): cmds.append("sudo rm -rf {}/*".format(local_path)) else: msg = "Directory {} will not be deleted for safety reasons.".format( local_path) display_message(msg, "reana") # execute commands for cmd in cmds: run_command(cmd, "reana")
def git_checkout(branch, component, fetch): # noqa: D301 """Check out given local branch in desired components. \b :param branch: Do you want to checkout some existing branch? [e.g. maint-0.7] :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 :param fetch: Should we fetch latest upstream first? [default=False] :type branch: str :type component: list :type fetch: bool """ for component in select_components(component): if fetch: run_command("git fetch upstream", component) if branch_exists(component, branch): run_command("git checkout {}".format(branch), component) else: click.secho( "No branch {} in component {}, staying on current one.".format( branch, component), fg="red", )
def git_clone(user, component, exclude_components): # noqa: D301 """Clone REANA source repositories from GitHub. If the ``user`` argument is provided, the ``origin`` will be cloned from the user repository on GitHub and the ``upstream`` will be set to ``reanahub`` organisation. Useful for setting up personal REANA development environment, If the ``user`` argument is not provided, the cloning will be done in anonymous manner from ``reanahub`` organisation. Also, the clone will be shallow to save disk space and CPU time. Useful for CI purposes. \b :param user: The GitHub user name. [default=anonymous] :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. :param exclude_components: List of components to exclude from command. :type user: str :type component: str :type exclude_components: str """ if exclude_components: exclude_components = exclude_components.split(",") components = select_components(component, exclude_components) for component in components: os.chdir(get_srcdir()) if os.path.exists("{0}/.git/config".format(component)): msg = "Component seems already cloned. Skipping." display_message(msg, component) elif user == "anonymous": cmd = "git clone https://github.com/reanahub/{0} --depth 1".format( component) run_command(cmd) else: cmd = "git clone [email protected]:{0}/{1}".format(user, component) run_command(cmd) for cmd in [ "git remote add upstream" ' "[email protected]:reanahub/{0}"'.format(component), "git config --add remote.upstream.fetch" ' "+refs/pull/*/head:refs/remotes/upstream/pr/*"', ]: run_command(cmd, component)
def git_fetch(component): # noqa: D301 """Fetch REANA upstream source code repositories without upgrade. \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 component in select_components(component): cmd = "git fetch upstream" run_command(cmd, component)
def compare_branches(branch_to_compare, current_branch): """Compare two branches with ``git rev-list``.""" cmd = "git branch -a | grep -c remotes/{}".format(branch_to_compare) try: check = subprocess.check_output(cmd, shell=True) except subprocess.CalledProcessError: check = 0 if check == 0: click.secho( "ERROR: Branch {} does not exist.".format(branch_to_compare), fg="red") return 0, 0 cmd = "git rev-list --left-right --count {0}...{1}".format( branch_to_compare, current_branch) behind, ahead = [ int(x) for x in run_command(cmd, display=False, return_output=True).split() ] return behind, ahead
def git_create_release_commit(component, base=GIT_DEFAULT_BASE_BRANCH, next_version=None): """Create a release commit for the given component.""" if "release:" in get_current_commit(get_srcdir(component)): display_message("Nothing to do, last commit is a release commit.", component) return False current_version = get_current_component_version_from_source_files( component) if not current_version and not next_version: display_message("Version cannot be autodiscovered from source files.", component) sys.exit(1) elif not git_is_current_version_tagged(component) and not next_version: display_message( f"Current version ({current_version}) " "not present as a git tag, please release it and add a tag.", component, ) sys.exit(1) next_version, modified_files = bump_component_version( component, current_version, next_version=next_version) if (run_command( "git branch --show-current", component, display=False, return_output=True, ) == base): run_command(f"git checkout -b release-{next_version}", component) if modified_files: run_command(f"git add {' '.join(modified_files)}", component) run_command( f"git commit -m 'release: {next_version}' {'--allow-empty' if not modified_files else ''}", component, ) return True