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
183 changes: 178 additions & 5 deletions src/subcommand/log_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#include <exception>
#include <format>
#include <git2.h>
#include <git2/revwalk.h>
#include <git2/oid.h>
#include <git2/refs.h>
#include <git2/types.h>
#include <string_view>
#include <vector>

#include <termcolor/termcolor.hpp>

Expand Down Expand Up @@ -50,15 +53,181 @@ void print_time(git_time intime, std::string prefix)
std::cout << prefix << out << " " << sign << std::format("{:02d}", hours) << std::format("{:02d}", minutes) <<std::endl;
}

void print_commit(const commit_wrapper& commit, std::string m_format_flag)
std::vector<std::string> get_tags_for_commit(repository_wrapper& repo, const git_oid* commit_oid)
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.

The commit_oid parameter should be passed by reference.

{
std::vector<std::string> tags;
git_strarray tag_names = {0};

if (git_tag_list(&tag_names, repo) != 0)
{
return tags;
}

for (size_t i = 0; i < tag_names.count; i++)
{
std::string tag_name = tag_names.strings[i];
std::string ref_name = "refs/tags/" + std::string(tag_name);

reference_wrapper tag_ref = repo.find_reference(ref_name);
object_wrapper peeled = tag_ref.peel<object_wrapper>();

if (git_oid_equal(&peeled.oid(), commit_oid))
{
tags.push_back(std::string(tag_name));
}
}

git_strarray_dispose(&tag_names);
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 is not exception-safe. However, we cannot use the git_strarray_wrapper here, since this class actually wraps a git_strarray whose elements point to already allocating strings (and thus, git_strarray_dispose is not called in the destructor to avoid double deletion).

Which leads me to think that the current git_strarray_wrapper is probably badly named. And that we should have a git_strarray_wrapper that frees both git_strarray elements and the array itself.

But this implies refactoring and goes beyond the scope of this PR, so let's add a TODO here and open an issue to track it when we merge this PR.

return tags;
}

std::vector<std::string> get_branches_for_commit(repository_wrapper& repo, git_branch_t type, const git_oid* commit_oid, const std::string exclude_branch)
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.

commit_oid should be passed by reference insteadof pointer.

{
std::vector<std::string> branches;

auto branch_iter = repo.iterate_branches(type);
while (auto branch = branch_iter.next())
{
const git_oid* branch_target = nullptr;
git_reference* ref = branch.value();

if (git_reference_type(ref) == GIT_REFERENCE_DIRECT)
{
branch_target = git_reference_target(ref);
}
else if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC)
{
git_reference* resolved = nullptr;
if (git_reference_resolve(&resolved, ref) == 0)
{
branch_target = git_reference_target(resolved);
git_reference_free(resolved);
}
}

if (branch_target && git_oid_equal(branch_target, commit_oid))
{
std::string branch_name;
branch_name = branch->name();
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.

Suggested change
std::string branch_name;
branch_name = branch->name();
std::string branch_name = branch->name();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I wrote it this way because it's not happy with the one line option:
error: conversion from 'std::string_view' {aka 'std::basic_string_view<char>'} to non-scalar type 'std::string' {aka 'std::__cxx11::basic_string<char>'} requested

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.

How about

std::string branch_name(branch->name());

instead, which works for me on macos?

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.

I missed that the return type of branch::name was std::string_view. In that case the correct solution is the line from Ian.

if (type == GIT_BRANCH_LOCAL)
{
if (branch_name != exclude_branch)
{
branches.push_back(branch_name);
}
}
else
{
branches.push_back(branch_name);
}
}
}

return branches;
}

struct commit_refs
{
std::string head_branch;
std::vector<std::string> tags;
std::vector<std::string> local_branches;
std::vector<std::string> remote_branches;

bool has_refs() const {
return !head_branch.empty() || !tags.empty() ||
!local_branches.empty() || !remote_branches.empty();
}
};

commit_refs get_refs_for_commit(repository_wrapper& repo, const git_oid* commit_oid)
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.

commit_oid should be passed by reference instead of pointer.

{
commit_refs refs;

if (!repo.is_head_unborn())
{
auto head = repo.head();
auto head_taget = head.target();
if (git_oid_equal(head_taget, commit_oid))
{
refs.head_branch = head.short_name();
}
}

refs.tags = get_tags_for_commit(repo, commit_oid);
refs.local_branches = get_branches_for_commit(repo, GIT_BRANCH_LOCAL, commit_oid, refs.head_branch);
refs.remote_branches = get_branches_for_commit(repo, GIT_BRANCH_REMOTE, commit_oid, "");

return refs;
}

void print_refs(commit_refs refs)
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.

refs should be passed by constant reference.

{
if (!refs.has_refs())
{
return;
}

std::cout << " (";

bool first = true;

if (!refs.head_branch.empty())
{
std::cout << termcolor::bold << termcolor::cyan << "HEAD" << termcolor::reset
<< termcolor::yellow << " -> " << termcolor::reset
<< termcolor::bold << termcolor::green << refs.head_branch << termcolor::reset
<< termcolor::yellow;
first = false;
}

for (const auto& tag :refs.tags)
{
if (!first)
{
std::cout << ", ";
}
std::cout << termcolor::bold << "tag: " << tag << termcolor::reset << termcolor::yellow;
first = false;
}

for (const auto& remote : refs.remote_branches)
{
if (!first)
{
std::cout << ", ";
}
std::cout << termcolor::bold << termcolor::red << remote << termcolor::reset << termcolor::yellow;
first = false;
}

for (const auto& local : refs.local_branches)
{
if (!first)
{
std::cout << ", ";
}
std::cout << termcolor::bold << termcolor::green << local << termcolor::reset << termcolor::yellow;
first = false;
}

std::cout << ")" << termcolor::reset;
}

void print_commit(repository_wrapper& repo, const commit_wrapper& commit, std::string m_format_flag)
{
std::string buf = commit.commit_oid_tostr();

signature_wrapper author = signature_wrapper::get_commit_author(commit);
signature_wrapper committer = signature_wrapper::get_commit_committer(commit);

stream_colour_fn colour = termcolor::yellow;
std::cout << colour << "commit " << buf << termcolor::reset << std::endl;
std::cout << colour << "commit " << buf;

commit_refs refs = get_refs_for_commit(repo, &commit.oid());
print_refs(refs);

std::cout << termcolor::reset << std::endl;

if (m_format_flag=="fuller")
{
std::cout << "Author:\t " << author.name() << " " << author.email() << std::endl;
Expand All @@ -78,7 +247,7 @@ void print_commit(const commit_wrapper& commit, std::string m_format_flag)
print_time(author.when(), "Date:\t");
}
}
std::cout << "\n " << commit.message() << "\n" << std::endl;
std::cout << "\n " << commit.message();
}

void log_subcommand::run()
Expand All @@ -102,8 +271,12 @@ void log_subcommand::run()
git_oid commit_oid;
while (!walker.next(commit_oid) && i<m_max_count_flag)
{
if (i != 0)
{
std::cout << std::endl;
}
commit_wrapper commit = repo.find_commit(commit_oid);
print_commit(commit, m_format_flag);
print_commit(repo, commit, m_format_flag);
++i;
}

Expand Down
Loading