Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This condition is already tested before calling create_tag, so this check can be removed.

{
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);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This method has a lot in common with create_lightweight_tag, maybe it would be worth extracting the common parts in small functions and have the create_XXX_tag be simple "drivers".

}

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