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
295 changes: 295 additions & 0 deletions src/subcommand/tag_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
#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;
}

size_t pos = 0;
int num = num_lines - 1; // TODO: check with git re. "- 1"

/** first line - headline */
size_t newline_pos = message.find('\n', pos);
if (newline_pos != std::string::npos)
{
std::cout << message.substr(pos, newline_pos - pos);
pos = newline_pos;
}
else
{
std::cout << message << std::endl;
return;
}

/** skip over new lines */
while (pos < message.length() && message[pos] == '\n')
{
pos++;
}

std::cout << std::endl;

/** print just headline? */
if (num == 0)
{
return;
}
if (pos < message.length() && pos + 1 < message.length())
Copy link
Member

Choose a reason for hiding this comment

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

If pos + 1 < message.length() is true then pos < message.length() must also be so it is not necessary to check both.

{
std::cout << std::endl;
}

/** print individual commit/tag lines */
while (pos < message.length() && num >= 2)
{
std::cout << " ";

newline_pos = message.find('\n', pos);
if (newline_pos != std::string::npos)
{
std::cout << message.substr(pos, newline_pos - pos);
pos = newline_pos;
}
else
{
std::cout << message.substr(pos);
break;
}

// Handle consecutive newlines
if (pos + 1 < message.length() &&
message[pos] == '\n' && message[pos + 1] == '\n')
{
num--;
std::cout << std::endl;
}

while (pos < message.length() && message[pos] == '\n')
{
pos++;
}

std::cout << std::endl;
num--;
}
}

// 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 (size_t i = 0; i < tag_names.size(); i++)
Copy link
Member

Choose a reason for hiding this comment

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

Alternatively we could avoid the loop variable here by using a range-based for loop like this instead:

for (const auto& tag_name: tag_names)
{
    each_tag(repo, tag_name, m_num_lines);
}

{
each_tag(repo, tag_names[i], 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;
}

void tag_subcommand::create_lightweight_tag(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);
}

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);

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 lightweight tag", error);
}
}

void tag_subcommand::create_tag(repository_wrapper& repo)
{
if (m_tag_name.empty())
{
throw git_exception("Tag name required", git2cpp_error_code::GENERIC_ERROR);
}

if (m_message.empty())
{
throw git_exception("Message required for annotated tag (use -m)", 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);
}

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);

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::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);
}

}
30 changes: 30 additions & 0 deletions src/subcommand/tag_subcommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#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::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;
};
7 changes: 7 additions & 0 deletions src/utils/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <unistd.h>
#include <map>

#include <git2.h>

#include "common.hpp"
#include "git_exception.hpp"

Expand Down Expand Up @@ -103,6 +105,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 Down
2 changes: 2 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 Down
Loading