diff --git a/CMakeLists.txt b/CMakeLists.txt index d9d648d..2f5acdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/main.cpp b/src/main.cpp index 223ddce..71140ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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" @@ -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); diff --git a/src/subcommand/log_subcommand.cpp b/src/subcommand/log_subcommand.cpp new file mode 100644 index 0000000..c25e270 --- /dev/null +++ b/src/subcommand/log_subcommand.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include + +#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 can be one of full and fuller"); + sub->add_option("-n,--max-count", m_max_count_flag, "Limit the output to 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) < +#include +#include + +#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::max(); + // bool m_oneline_flag = false; +}; diff --git a/src/wrapper/commit_wrapper.cpp b/src/wrapper/commit_wrapper.cpp index bbd48b1..a5996fc 100644 --- a/src/wrapper/commit_wrapper.cpp +++ b/src/wrapper/commit_wrapper.cpp @@ -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()); +} diff --git a/src/wrapper/commit_wrapper.hpp b/src/wrapper/commit_wrapper.hpp index 2cf2378..d7930d9 100644 --- a/src/wrapper/commit_wrapper.hpp +++ b/src/wrapper/commit_wrapper.hpp @@ -19,6 +19,7 @@ class commit_wrapper : public wrapper_base operator git_object*() const noexcept; const git_oid& oid() const; + std::string commit_oid_tostr() const; private: diff --git a/src/wrapper/signature_wrapper.cpp b/src/wrapper/signature_wrapper.cpp index 4bbb7a6..d16eaf9 100644 --- a/src/wrapper/signature_wrapper.cpp +++ b/src/wrapper/signature_wrapper.cpp @@ -4,10 +4,28 @@ 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; @@ -15,3 +33,19 @@ signature_wrapper::author_committer_signatures signature_wrapper::get_default_si 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_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_commit_committer(cw)); + committer.m_ownership = false; + return committer; +} diff --git a/src/wrapper/signature_wrapper.hpp b/src/wrapper/signature_wrapper.hpp index 150bf62..2ebc861 100644 --- a/src/wrapper/signature_wrapper.hpp +++ b/src/wrapper/signature_wrapper.hpp @@ -1,11 +1,13 @@ #pragma once #include +#include #include #include "../wrapper/wrapper_base.hpp" +class commit_wrapper; class repository_wrapper; class signature_wrapper : public wrapper_base @@ -18,9 +20,16 @@ class signature_wrapper : public wrapper_base 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; }; diff --git a/test/test_log.py b/test/test_log.py new file mode 100644 index 0000000..659c7a4 --- /dev/null +++ b/test/test_log.py @@ -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