Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/subcommand/commit_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "subcommand/clone_subcommand.hpp"
#include "subcommand/commit_subcommand.hpp"
#include "subcommand/init_subcommand.hpp"
#include "subcommand/log_subcommand.hpp"
#include "subcommand/reset_subcommand.hpp"
#include "subcommand/status_subcommand.hpp"

Expand All @@ -33,6 +34,7 @@ int main(int argc, char** argv)
clone_subcommand clone(lg2_obj, app);
commit_subcommand commit(lg2_obj, app);
reset_subcommand reset(lg2_obj, app);
log_subcommand log(lg2_obj, app);

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

Expand Down
101 changes: 101 additions & 0 deletions src/subcommand/log_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include <format>
#include <git2.h>
#include <git2/revwalk.h>
#include <git2/types.h>
#include <string_view>

#include "log_subcommand.hpp"
#include "../wrapper/repository_wrapper.hpp"
#include "../wrapper/commit_wrapper.hpp"

log_subcommand::log_subcommand(const libgit2_object&, CLI::App& app)
{
auto *sub = app.add_subcommand("log", "Shows commit logs");

sub->add_flag("--format", m_format_flag, "Pretty-print the contents of the commit logs in a given format, where <format> can be one of full and fuller");
sub->add_option("-n,--max-count", m_max_count_flag, "Limit the output to <number> commits.");
// sub->add_flag("--oneline", m_oneline_flag, "This is a shorthand for --pretty=oneline --abbrev-commit used together.");

sub->callback([this]() { this->run(); });
};

void print_time(git_time intime, std::string prefix)
{
char sign, out[32];
struct tm *intm;
int offset, hours, minutes;
time_t t;

offset = intime.offset;
if (offset < 0) {
sign = '-';
offset = -offset;
}
else
{
sign = '+';
}

hours = offset / 60;
minutes = offset % 60;

t = (time_t)intime.time + (intime.offset * 60);

intm = gmtime(&t);
strftime(out, sizeof(out), "%a %b %e %T %Y", intm);

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::string buf = commit.commit_oid_tostr();

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

std::cout << "\033[0;33m" << "commit " << buf << "\033[0m" << std::endl;
if (m_format_flag=="fuller")
{
std::cout << "Author:\t " << author.name() << " " << author.email() << std::endl;
print_time(author.when(), "AuthorDate: ");
std::cout << "Commit:\t " << committer.name() << " " << committer.email() << std::endl;
print_time(committer.when(), "CommitDate: ");
}
else
{
std::cout << "Author:\t" << author.name() << " " << author.email() << std::endl;
if (m_format_flag=="full")
{
std::cout << "Commit:\t" << committer.name() << " " << committer.email() << std::endl;
}
else
{
print_time(author.when(), "Date:\t");
}
}
std::cout << "\n " << git_commit_message(commit) << "\n" << std::endl;
}

void log_subcommand::run()
{
auto directory = get_current_git_path();
auto bare = false;
auto repo = repository_wrapper::init(directory, bare);
// auto branch_name = repo.head().short_name();

git_revwalk* walker;
git_revwalk_new(&walker, repo);
git_revwalk_push_head(walker);

std::size_t i=0;
git_oid commit_oid;
while (!git_revwalk_next(&commit_oid, walker) && i<m_max_count_flag)
{
commit_wrapper commit = repo.find_commit(commit_oid);
print_commit(commit, m_format_flag);
++i;
}

git_revwalk_free(walker);
}
21 changes: 21 additions & 0 deletions src/subcommand/log_subcommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include <CLI/CLI.hpp>
#include <cstddef>
#include <limits>

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


class log_subcommand
{
public:

explicit log_subcommand(const libgit2_object&, CLI::App& app);
void run();

private:
std::string m_format_flag;
int m_max_count_flag=std::numeric_limits<int>::max();
// bool m_oneline_flag = false;
};
6 changes: 6 additions & 0 deletions src/wrapper/commit_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ const git_oid& commit_wrapper::oid() const
{
return *git_commit_id(p_resource);
}

std::string commit_wrapper::commit_oid_tostr() const
{
char buf[GIT_OID_SHA1_HEXSIZE + 1];
return git_oid_tostr(buf, sizeof(buf), &this->oid());
}
1 change: 1 addition & 0 deletions src/wrapper/commit_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class commit_wrapper : public wrapper_base<git_commit>
operator git_object*() const noexcept;

const git_oid& oid() const;
std::string commit_oid_tostr() const;

private:

Expand Down
36 changes: 35 additions & 1 deletion src/wrapper/signature_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,48 @@

signature_wrapper::~signature_wrapper()
{
git_signature_free(p_resource);
if (m_ownership)
{
git_signature_free(p_resource);
}
p_resource=nullptr;
}

std::string_view signature_wrapper::name() const
{
return p_resource->name;
}

std::string_view signature_wrapper::email() const
{
return p_resource->email;
}

git_time signature_wrapper::when() const
{
return p_resource->when;
}

signature_wrapper::author_committer_signatures signature_wrapper::get_default_signature_from_env(repository_wrapper& rw)
{
signature_wrapper author;
signature_wrapper committer;
throw_if_error(git_signature_default_from_env(&(author.p_resource), &(committer.p_resource), rw));
return {std::move(author), std::move(committer)};
}

signature_wrapper signature_wrapper::get_commit_author(const commit_wrapper& cw)
{
signature_wrapper author;
author.p_resource = const_cast<git_signature*>(git_commit_author(cw));
author.m_ownership = false;
return author;
}

signature_wrapper signature_wrapper::get_commit_committer(const commit_wrapper& cw)
{
signature_wrapper committer;
committer.p_resource = const_cast<git_signature*>(git_commit_committer(cw));
committer.m_ownership = false;
return committer;
}
9 changes: 9 additions & 0 deletions src/wrapper/signature_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once

#include <utility>
#include <string_view>

#include <git2.h>

#include "../wrapper/wrapper_base.hpp"

class commit_wrapper;
class repository_wrapper;

class signature_wrapper : public wrapper_base<git_signature>
Expand All @@ -18,9 +20,16 @@ class signature_wrapper : public wrapper_base<git_signature>
signature_wrapper(signature_wrapper&&) = default;
signature_wrapper& operator=(signature_wrapper&&) = default;

std::string_view name() const;
std::string_view email() const;
git_time when() const;

static author_committer_signatures get_default_signature_from_env(repository_wrapper&);
static signature_wrapper get_commit_author(const commit_wrapper&);
static signature_wrapper get_commit_committer(const commit_wrapper&);

private:

signature_wrapper() = default;
bool m_ownership=true;
};
54 changes: 54 additions & 0 deletions test/test_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import subprocess

import pytest

@pytest.mark.parametrize("format_flag", ["", "--format=full", "--format=fuller"])
def test_log(xtl_clone, git_config, git2cpp_path, tmp_path, monkeypatch, format_flag):
assert (tmp_path / "xtl").exists()
xtl_path = tmp_path / "xtl"

p = xtl_path / "mook_file.txt"
p.write_text('')

cmd_add = [git2cpp_path, 'add', "mook_file.txt"]
p_add = subprocess.run(cmd_add, cwd=xtl_path, text=True)
assert p_add.returncode == 0

cmd_commit = [git2cpp_path, 'commit', "-m", "test commit"]
p_commit = subprocess.run(cmd_commit, cwd=xtl_path, text=True)
assert p_commit.returncode == 0

cmd_log = [git2cpp_path, 'log']
if format_flag != "":
cmd_log.append(format_flag)
p_log = subprocess.run(cmd_log, capture_output=True, cwd=xtl_path, text=True)
assert p_log.returncode == 0
assert "Jane Doe" in p_log.stdout
assert "test commit" in p_log.stdout

if format_flag == "":
assert "Commit" not in p_log.stdout
else:
assert "Commit" in p_log.stdout
if format_flag == "--format=full":
assert "Date" not in p_log.stdout
else:
assert "CommitDate" in p_log.stdout


@pytest.mark.parametrize("max_count_flag", ["", "-n", "--max-count"])
def test_max_count(xtl_clone, git_config, git2cpp_path, tmp_path, monkeypatch, max_count_flag):
assert (tmp_path / "xtl").exists()
xtl_path = tmp_path / "xtl"

cmd_log = [git2cpp_path, 'log']
if max_count_flag != "":
cmd_log.append(max_count_flag)
cmd_log.append("2")
p_log = subprocess.run(cmd_log, capture_output=True, cwd=xtl_path, text=True)
assert p_log.returncode == 0

if max_count_flag == "":
assert p_log.stdout.count("Author") > 2
else:
assert p_log.stdout.count("Author") == 2