Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/subcommand/stash_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/tag_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/tag_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.cpp
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.hpp
${GIT2CPP_SOURCE_DIR}/utils/common.cpp
Expand Down Expand Up @@ -126,6 +128,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/wrapper/signature_wrapper.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/status_wrapper.cpp
${GIT2CPP_SOURCE_DIR}/wrapper/status_wrapper.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/tag_wrapper.cpp
${GIT2CPP_SOURCE_DIR}/wrapper/tag_wrapper.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/tree_wrapper.cpp
${GIT2CPP_SOURCE_DIR}/wrapper/tree_wrapper.hpp
${GIT2CPP_SOURCE_DIR}/wrapper/wrapper_base.hpp
Expand Down
2 changes: 2 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "subcommand/reset_subcommand.hpp"
#include "subcommand/stash_subcommand.hpp"
#include "subcommand/status_subcommand.hpp"
#include "subcommand/tag_subcommand.hpp"
#include "subcommand/revparse_subcommand.hpp"
#include "subcommand/revlist_subcommand.hpp"
#include "subcommand/rm_subcommand.hpp"
Expand Down Expand Up @@ -60,6 +61,7 @@ int main(int argc, char** argv)
revparse_subcommand revparse(lg2_obj, app);
rm_subcommand rm(lg2_obj, app);
stash_subcommand stash(lg2_obj, app);
tag_subcommand tag(lg2_obj, app);

app.require_subcommand(/* min */ 0, /* max */ 1);

Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/log_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void print_commit(const commit_wrapper& commit, std::string m_format_flag)
print_time(author.when(), "Date:\t");
}
}
std::cout << "\n " << git_commit_message(commit) << "\n" << std::endl;
std::cout << "\n " << commit.message() << "\n" << std::endl;
}

void log_subcommand::run()
Expand Down
237 changes: 237 additions & 0 deletions src/subcommand/tag_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#include <git2.h>

#include "../subcommand/tag_subcommand.hpp"
#include "../wrapper/commit_wrapper.hpp"
#include "../wrapper/tag_wrapper.hpp"

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 <pattern>.");
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, "<num> specifies how many lines from the annotation, if any, are printed when using -l. Implies --list.");
sub->add_option("-m,--message", m_message, "Tag message for annotated tags");
sub->add_option("<tagname>", m_tag_name, "Tag name");
sub->add_option("<commit>", 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function looks quite complicated for a relatively simple task. Maybe a better approach would be to first split the string at newline characters into a vector of strings and walk that vector. There is already a split-string-at-newlines function at

void terminal_pager::split_input_at_newlines(std::string_view str)

which could be pulled out of that file and reused.

But that could be in a later separate PR.

{
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<object_wrapper> 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()))
{
list_tags(repo);
}
else if (!m_message.empty())
{
create_tag(repo);
}
else if (!m_tag_name.empty())
{
create_lightweight_tag(repo);
}
else
{
list_tags(repo);
}

}
32 changes: 32 additions & 0 deletions src/subcommand/tag_subcommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <CLI/CLI.hpp>

#include "../utils/common.hpp"
#include "../wrapper/repository_wrapper.hpp"

class tag_subcommand
{
public:

explicit tag_subcommand(const libgit2_object&, CLI::App& app);

void run();

private:

void list_tags(repository_wrapper& repo);
void delete_tag(repository_wrapper& repo);
void create_lightweight_tag(repository_wrapper& repo);
void create_tag(repository_wrapper& repo);
std::optional<object_wrapper> get_target_obj(repository_wrapper& repo);
void handle_error(int error);

std::string m_delete;
std::string m_message;
std::string m_tag_name;
std::string m_target;
bool m_list_flag = false;
bool m_force_flag = false;
int m_num_lines = 0;
};
17 changes: 17 additions & 0 deletions src/utils/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <sstream>
#include <unistd.h>
#include <map>
#include <ranges>

#include <git2.h>

#include "common.hpp"
#include "git_exception.hpp"
Expand Down Expand Up @@ -103,6 +106,11 @@ void git_strarray_wrapper::init_str_array()
}
}

size_t git_strarray_wrapper::size()
{
return m_patterns.size();
}

std::string read_file(const std::string& path)
{
std::ifstream file(path, std::ios::binary);
Expand All @@ -114,3 +122,12 @@ std::string read_file(const std::string& path)
buffer << file.rdbuf();
return buffer.str();
}

std::vector<std::string> split_input_at_newlines(std::string_view str)
{
auto split = str | std::ranges::views::split('\n')
| std::ranges::views::transform([](auto&& range) {
return std::string(range.begin(), range.end());
});
return std::vector<std::string>{split.begin(), split.end()};
}
4 changes: 4 additions & 0 deletions src/utils/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class git_strarray_wrapper

operator git_strarray*();

size_t size();

private:
std::vector<std::string> m_patterns;
git_strarray m_array;
Expand All @@ -66,3 +68,5 @@ class git_strarray_wrapper
};

std::string read_file(const std::string& path);

std::vector<std::string> split_input_at_newlines(std::string_view str);
12 changes: 2 additions & 10 deletions src/utils/terminal_pager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "ansi_code.hpp"
#include "output.hpp"
#include "terminal_pager.hpp"
#include "common.hpp"

terminal_pager::terminal_pager()
: m_rows(0), m_columns(0), m_start_row_index(0)
Expand Down Expand Up @@ -167,7 +168,7 @@ void terminal_pager::show()
{
release_cout();

split_input_at_newlines(m_stringbuf.view());
m_lines = split_input_at_newlines(m_stringbuf.view());

update_terminal_size();
if (m_rows == 0 || m_lines.size() <= m_rows - 1)
Expand Down Expand Up @@ -196,15 +197,6 @@ void terminal_pager::show()
m_start_row_index = 0;
}

void terminal_pager::split_input_at_newlines(std::string_view str)
{
auto split = str | std::ranges::views::split('\n')
| std::ranges::views::transform([](auto&& range) {
return std::string(range.begin(), range.end());
});
m_lines = std::vector<std::string>{split.begin(), split.end()};
}

void terminal_pager::update_terminal_size()
{
struct winsize size;
Expand Down
Loading