#include "../subcommand/tag_subcommand.hpp"
#include
tag_subcommand::tag_subcommand(const libgit2_object&, CLI::App& app)
{
auto* sub = app.add_subcommand("tag", "Create, list, delete or verify tags");
sub->add_flag("-l,--list", m_list_flag, "List tags. With optional .");
sub->add_flag("-f,--force", m_force_flag, "Replace an existing tag with the given name (instead of failing)");
sub->add_option("-d,--delete", m_delete, "Delete existing tags with the given names.");
sub->add_option(
"-n",
m_num_lines,
" specifies how many lines from the annotation, if any, are printed when using -l. Implies --list."
);
sub->add_flag("-a,--annotate", m_annotate_flag, "Make an annotated tag.");
sub->add_option("-m,--message", m_message, "Tag message for annotated tags");
sub->add_option("", m_tag_name, "Tag name");
sub->add_option("", m_target, "Target commit (defaults to HEAD)");
sub->callback(
[this]()
{
this->run();
}
);
}
// Tag listing: Print individual message lines
void print_list_lines(const std::string& message, int num_lines)
{
if (message.empty())
{
return;
}
auto lines = split_input_at_newlines(message);
// header
std::cout << lines[0];
// other lines
if (num_lines <= 1 || lines.size() <= 2)
{
std::cout << std::endl;
}
else
{
for (size_t i = 1; i < lines.size(); i++)
{
if (i < num_lines)
{
std::cout << "\n\t\t" << lines[i];
}
}
}
}
// Tag listing: Print an actual tag object
void print_tag(git_tag* tag, int num_lines)
{
std::cout << std::left << std::setw(16) << git_tag_name(tag);
if (num_lines)
{
std::string msg = git_tag_message(tag);
if (!msg.empty())
{
print_list_lines(msg, num_lines);
}
else
{
std::cout << std::endl;
}
}
else
{
std::cout << std::endl;
}
}
// Tag listing: Print a commit (target of a lightweight tag)
void print_commit(git_commit* commit, std::string name, int num_lines)
{
std::cout << std::left << std::setw(16) << name;
if (num_lines)
{
std::string msg = git_commit_message(commit);
if (!msg.empty())
{
print_list_lines(msg, num_lines);
}
else
{
std::cout << std::endl;
}
}
else
{
std::cout << std::endl;
}
}
// Tag listing: Lookup tags based on ref name and dispatch to print
void each_tag(repository_wrapper& repo, const std::string& name, int num_lines)
{
auto obj = repo.revparse_single(name);
if (obj.has_value())
{
switch (git_object_type(obj.value()))
{
case GIT_OBJECT_TAG:
print_tag(obj.value(), num_lines);
break;
case GIT_OBJECT_COMMIT:
print_commit(obj.value(), name, num_lines);
break;
default:
std::cout << name << std::endl;
}
}
else
{
std::cout << name << std::endl;
}
}
void tag_subcommand::list_tags(repository_wrapper& repo)
{
std::string pattern = m_tag_name.empty() ? "*" : m_tag_name;
auto tag_names = repo.tag_list_match(pattern);
for (const auto& tag_name : tag_names)
{
each_tag(repo, tag_name, m_num_lines);
}
}
void tag_subcommand::delete_tag(repository_wrapper& repo)
{
if (m_delete.empty())
{
throw git_exception("Name required for tag deletion.", git2cpp_error_code::GENERIC_ERROR);
}
auto obj = repo.revparse_single(m_delete);
if (!obj.has_value())
{
throw git_exception("error: tag '" + m_delete + "' not found.", git2cpp_error_code::GENERIC_ERROR);
}
git_buf abbrev_oid = GIT_BUF_INIT;
throw_if_error(git_object_short_id(&abbrev_oid, obj.value()));
std::string oid_str(abbrev_oid.ptr);
git_buf_dispose(&abbrev_oid);
throw_if_error(git_tag_delete(repo, m_delete.c_str()));
std::cout << "Deleted tag '" << m_delete << "' (was " << oid_str << ")" << std::endl;
}
std::optional tag_subcommand::get_target_obj(repository_wrapper& repo)
{
if (m_tag_name.empty())
{
throw git_exception("Tag name required", git2cpp_error_code::GENERIC_ERROR);
}
std::string target = m_target.empty() ? "HEAD" : m_target;
auto target_obj = repo.revparse_single(target);
if (!target_obj.has_value())
{
throw git_exception("Unable to resolve target: " + target, git2cpp_error_code::GENERIC_ERROR);
}
return target_obj;
}
void tag_subcommand::handle_error(int error)
{
if (error < 0)
{
if (error == GIT_EEXISTS)
{
throw git_exception("tag '" + m_tag_name + "' already exists", git2cpp_error_code::FILESYSTEM_ERROR);
}
throw git_exception("Unable to create annotated tag", error);
}
}
void tag_subcommand::create_lightweight_tag(repository_wrapper& repo)
{
auto target_obj = tag_subcommand::get_target_obj(repo);
git_oid oid;
size_t force = m_force_flag ? 1 : 0;
int error = git_tag_create_lightweight(&oid, repo, m_tag_name.c_str(), target_obj.value(), force);
handle_error(error);
}
void tag_subcommand::create_tag(repository_wrapper& repo)
{
auto target_obj = tag_subcommand::get_target_obj(repo);
auto tagger = signature_wrapper::get_default_signature_from_env(repo);
git_oid oid;
size_t force = m_force_flag ? 1 : 0;
int error = git_tag_create(
&oid,
repo,
m_tag_name.c_str(),
target_obj.value(),
tagger.first,
m_message.c_str(),
force
);
handle_error(error);
}
void tag_subcommand::run()
{
auto directory = get_current_git_path();
auto repo = repository_wrapper::open(directory);
if (!m_delete.empty())
{
delete_tag(repo);
}
else if (m_list_flag || (m_tag_name.empty() && m_message.empty() && !m_annotate_flag))
{
list_tags(repo);
}
else if (m_annotate_flag)
{
if (m_message.empty())
{
throw git_exception("error: -a/--annotate requires -m/--message", git2cpp_error_code::BAD_ARGUMENT);
}
create_tag(repo);
}
else if (!m_message.empty())
{
create_tag(repo);
}
else if (!m_tag_name.empty())
{
create_lightweight_tag(repo);
}
else
{
list_tags(repo);
}
}