"""
Reference-management CLI commands for :mod:`hubvault.entry`.
This module exposes git-like branch and tag commands on top of the public
``hubvault`` API.
The module contains:
* :func:`register_ref_commands` - Register branch and tag commands on a Click group
"""
from typing import Dict, Optional
import click
from ..models import GitCommitInfo
from .base import ClickErrorException, command_wrap
from .context import load_cli_repo_context
from .formatters import format_branch_output, short_oid
from .style import echo, style_text
[docs]
def register_ref_commands(group: click.Group) -> click.Group:
"""
Register branch and tag commands on a Click command group.
:param group: Click group receiving the registered commands
:type group: click.Group
:return: The same Click group for decorator chaining
:rtype: click.Group
Example::
>>> import click
>>> group = click.Group()
>>> register_ref_commands(group) is group
True
"""
@group.command("branch")
@click.option("-v", "--verbose", is_flag=True, help="Show commit and subject for each branch.")
@click.option("--show-current", is_flag=True, help="Show the current branch name.")
@click.option("-d", "delete_mode", is_flag=True, help="Delete a branch.")
@click.option("-D", "force_delete", is_flag=True, help="Force-delete a branch.")
@click.argument("branch", required=False)
@click.argument("start_point", required=False)
@click.pass_context
@command_wrap()
def branch_command(
ctx: click.Context,
verbose: bool,
show_current: bool,
delete_mode: bool,
force_delete: bool,
branch: Optional[str],
start_point: Optional[str],
) -> None:
"""
List, create, or delete branches.
:param ctx: Click context for the current command
:type ctx: click.Context
:param verbose: Whether verbose listing is requested
:type verbose: bool
:param show_current: Whether the current branch name should be printed
:type show_current: bool
:param delete_mode: Whether branch deletion is requested
:type delete_mode: bool
:param force_delete: Whether forced branch deletion is requested
:type force_delete: bool
:param branch: Branch name to create/delete
:type branch: Optional[str]
:param start_point: Optional revision used as the new branch base
:type start_point: Optional[str]
:return: ``None``.
:rtype: None
"""
repo_context = load_cli_repo_context(ctx)
api = repo_context.create_api()
refs = api.list_repo_refs()
if show_current:
echo(repo_context.default_branch)
return
if delete_mode or force_delete:
if not branch:
raise ClickErrorException("branch -d/-D requires a branch name.")
target_oid = None
for ref in refs.branches:
if ref.name == branch:
target_oid = ref.target_commit
break
api.delete_branch(branch=branch)
if target_oid is None:
echo("Deleted branch {branch}.".format(branch=branch), tone="success")
else:
echo(
"Deleted branch {branch} (was {oid}).".format(
branch=branch,
oid=style_text(short_oid(target_oid), tone="accent"),
)
)
return
if branch:
api.create_branch(branch=branch, revision=start_point or repo_context.default_branch)
return
branch_names = [ref.name for ref in refs.branches]
commit_map = {} # type: Dict[str, Optional[GitCommitInfo]]
if verbose:
for ref in refs.branches:
if ref.target_commit is None:
commit_map[ref.name] = None
else:
commit_map[ref.name] = api.list_repo_commits(revision=ref.name)[:1][0]
echo(
format_branch_output(
branch_names=branch_names,
current_branch=repo_context.default_branch,
commit_map=commit_map,
verbose=verbose,
)
)
@group.command("tag")
@click.option("-l", "--list", "list_mode", is_flag=True, help="List tags.")
@click.option("-d", "delete_mode", is_flag=True, help="Delete a tag.")
@click.option("-m", "--message", "message", default=None, help="Optional tag message.")
@click.argument("tag", required=False)
@click.argument("revision", required=False)
@click.pass_context
@command_wrap()
def tag_command(
ctx: click.Context,
list_mode: bool,
delete_mode: bool,
message: Optional[str],
tag: Optional[str],
revision: Optional[str],
) -> None:
"""
List, create, or delete tags.
:param ctx: Click context for the current command
:type ctx: click.Context
:param list_mode: Whether tag listing is requested
:type list_mode: bool
:param delete_mode: Whether tag deletion is requested
:type delete_mode: bool
:param message: Optional tag message
:type message: Optional[str]
:param tag: Tag name to create/delete
:type tag: Optional[str]
:param revision: Optional revision used for tag creation
:type revision: Optional[str]
:return: ``None``.
:rtype: None
"""
repo_context = load_cli_repo_context(ctx)
api = repo_context.create_api()
refs = api.list_repo_refs()
if delete_mode:
if not tag:
raise ClickErrorException("tag -d requires a tag name.")
target_oid = None
for ref in refs.tags:
if ref.name == tag:
target_oid = ref.target_commit
break
api.delete_tag(tag=tag)
if target_oid is None:
echo("Deleted tag '{tag}'.".format(tag=tag), tone="success")
else:
echo(
"Deleted tag '{tag}' (was {oid}).".format(
tag=tag,
oid=style_text(short_oid(target_oid), tone="accent"),
)
)
return
if tag and not list_mode:
api.create_tag(
tag=tag,
revision=revision or repo_context.default_branch,
tag_message=message,
)
return
lines = [ref.name for ref in refs.tags]
if lines:
echo("\n".join(lines))
return group