From a509df8a79a64a4b6d1dad051bf33063015e5871 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Tue, 26 Aug 2025 17:26:03 +0200 Subject: [PATCH 1/6] add log subcommand --- CMakeLists.txt | 2 + src/main.cpp | 2 + src/subcommand/log_subcommand.cpp | 88 +++++++++++++++++++++++++++++++ src/subcommand/log_subcommand.hpp | 18 +++++++ src/wrapper/signature_wrapper.cpp | 29 ++++++++++ src/wrapper/signature_wrapper.hpp | 8 +++ 6 files changed, 147 insertions(+) create mode 100644 src/subcommand/log_subcommand.cpp create mode 100644 src/subcommand/log_subcommand.hpp 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..8264899 --- /dev/null +++ b/src/subcommand/log_subcommand.cpp @@ -0,0 +1,88 @@ +// #include +// #include +// #include + +#include +#include +#include + +#include "log_subcommand.hpp" +#include "../wrapper/repository_wrapper.hpp" + +// TODO: put in another file +/** Size (in bytes) of a raw/binary sha1 oid */ +#define GIT_OID_SHA1_SIZE 20 +/** Size (in bytes) of a hex formatted sha1 oid */ +#define GIT_OID_SHA1_HEXSIZE (GIT_OID_SHA1_SIZE * 2) + +log_subcommand::log_subcommand(const libgit2_object&, CLI::App& app) +{ + auto *sub = app.add_subcommand("log", "Shows commit logs"); + + sub->add_flag("-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, const char *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); + + printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); +} + +void print_commit(const commit_wrapper& commit) +{ + // TODO: put in commit_wrapper ? + char buf[GIT_OID_SHA1_HEXSIZE + 1]; + int i, count; + + git_oid_tostr(buf, sizeof(buf), &commit.oid()); + // TODO end + + signature_wrapper author = signature_wrapper::get_commit_author(commit); + + std::cout << "commit " << buf << std::endl; + std::cout << "Author:\t" << author.name() << " " << author.email() << std::endl; + print_time(author.when(), "Date:\t"); + std::cout << 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); + + git_oid commit_oid; + while (!git_revwalk_next(&commit_oid, walker)) + { + commit_wrapper commit = repo.find_commit(commit_oid); + print_commit(commit); + } +} diff --git a/src/subcommand/log_subcommand.hpp b/src/subcommand/log_subcommand.hpp new file mode 100644 index 0000000..49634da --- /dev/null +++ b/src/subcommand/log_subcommand.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "../utils/common.hpp" + + +class log_subcommand +{ +public: + + explicit log_subcommand(const libgit2_object&, CLI::App& app); + void run(); + +private: + int m_max_count_flag; + // bool m_oneline_flag = false; +}; diff --git a/src/wrapper/signature_wrapper.cpp b/src/wrapper/signature_wrapper.cpp index 4bbb7a6..6eb9fb9 100644 --- a/src/wrapper/signature_wrapper.cpp +++ b/src/wrapper/signature_wrapper.cpp @@ -8,6 +8,21 @@ signature_wrapper::~signature_wrapper() 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 +30,17 @@ 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)); + return author; +} + +signature_wrapper signature_wrapper::get_commit_committer(const commit_wrapper& cw) +{ + signature_wrapper author; + author.p_resource = const_cast(git_commit_committer(cw)); + return author; +} diff --git a/src/wrapper/signature_wrapper.hpp b/src/wrapper/signature_wrapper.hpp index 150bf62..1c73c1e 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,7 +20,13 @@ 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: From 16cfe1cd8772a1f9282551ed4f1e538d7320dce8 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Tue, 26 Aug 2025 18:14:49 +0200 Subject: [PATCH 2/6] add committer option --- src/subcommand/log_subcommand.cpp | 25 ++++++++++++++++++++----- src/subcommand/log_subcommand.hpp | 3 ++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/subcommand/log_subcommand.cpp b/src/subcommand/log_subcommand.cpp index 8264899..64f9507 100644 --- a/src/subcommand/log_subcommand.cpp +++ b/src/subcommand/log_subcommand.cpp @@ -19,7 +19,8 @@ log_subcommand::log_subcommand(const libgit2_object&, CLI::App& app) { auto *sub = app.add_subcommand("log", "Shows commit logs"); - sub->add_flag("-n,--max-count", m_max_count_flag, "Limit the output to commits."); + 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_flag("-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(); }); @@ -51,7 +52,7 @@ void print_time(git_time intime, const char *prefix) printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); } -void print_commit(const commit_wrapper& commit) +void print_commit(const commit_wrapper& commit, std::string m_format_flag) { // TODO: put in commit_wrapper ? char buf[GIT_OID_SHA1_HEXSIZE + 1]; @@ -61,10 +62,24 @@ void print_commit(const commit_wrapper& commit) // TODO end signature_wrapper author = signature_wrapper::get_commit_author(commit); + signature_wrapper committer = signature_wrapper::get_commit_committer(commit); - std::cout << "commit " << buf << std::endl; + std::cout << "\033[0;33m" << "commit " << buf << "\033[0m" << std::endl; std::cout << "Author:\t" << author.name() << " " << author.email() << std::endl; - print_time(author.when(), "Date:\t"); + if (m_format_flag=="full") + { + std::cout << "Commit:\t" << committer.name() << " " << committer.email() << std::endl; + } + else if (m_format_flag=="fuller") + { + print_time(author.when(), "AuthorDate:\t"); + std::cout << "Commit:\t" << committer.name() << " " << committer.email() << std::endl; + print_time(committer.when(), "CommitDate:\t"); + } + else + { + print_time(author.when(), "Date:\t"); + } std::cout << git_commit_message(commit) << "\n" << std::endl; } @@ -83,6 +98,6 @@ void log_subcommand::run() while (!git_revwalk_next(&commit_oid, walker)) { commit_wrapper commit = repo.find_commit(commit_oid); - print_commit(commit); + print_commit(commit, m_format_flag); } } diff --git a/src/subcommand/log_subcommand.hpp b/src/subcommand/log_subcommand.hpp index 49634da..c4f211b 100644 --- a/src/subcommand/log_subcommand.hpp +++ b/src/subcommand/log_subcommand.hpp @@ -13,6 +13,7 @@ class log_subcommand void run(); private: - int m_max_count_flag; + std::string m_format_flag; + // int m_max_count_flag; // bool m_oneline_flag = false; }; From b08b8caacf0a2dea00ffa1c0d8e4c2ec328b9e54 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 27 Aug 2025 13:47:08 +0200 Subject: [PATCH 3/6] fix memry issue and add test --- src/subcommand/log_subcommand.cpp | 54 ++++++++++++++----------------- src/wrapper/commit_wrapper.cpp | 6 ++++ src/wrapper/commit_wrapper.hpp | 1 + src/wrapper/signature_wrapper.cpp | 13 +++++--- src/wrapper/signature_wrapper.hpp | 1 + test/test_log.py | 36 +++++++++++++++++++++ 6 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 test/test_log.py diff --git a/src/subcommand/log_subcommand.cpp b/src/subcommand/log_subcommand.cpp index 64f9507..ab574f8 100644 --- a/src/subcommand/log_subcommand.cpp +++ b/src/subcommand/log_subcommand.cpp @@ -1,19 +1,12 @@ -// #include -// #include -// #include - +#include #include #include #include +#include #include "log_subcommand.hpp" #include "../wrapper/repository_wrapper.hpp" - -// TODO: put in another file -/** Size (in bytes) of a raw/binary sha1 oid */ -#define GIT_OID_SHA1_SIZE 20 -/** Size (in bytes) of a hex formatted sha1 oid */ -#define GIT_OID_SHA1_HEXSIZE (GIT_OID_SHA1_SIZE * 2) +#include "../wrapper/commit_wrapper.hpp" log_subcommand::log_subcommand(const libgit2_object&, CLI::App& app) { @@ -26,7 +19,7 @@ log_subcommand::log_subcommand(const libgit2_object&, CLI::App& app) sub->callback([this]() { this->run(); }); }; -void print_time(git_time intime, const char *prefix) +void print_time(git_time intime, std::string prefix) { char sign, out[32]; struct tm *intm; @@ -37,7 +30,9 @@ void print_time(git_time intime, const char *prefix) if (offset < 0) { sign = '-'; offset = -offset; - } else { + } + else + { sign = '+'; } @@ -49,38 +44,37 @@ void print_time(git_time intime, const char *prefix) intm = gmtime(&t); strftime(out, sizeof(out), "%a %b %e %T %Y", intm); - printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); + std::cout << prefix << out << " " << sign << std::format("{:02d}", hours) << std::format("{:02d}", minutes) <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 6eb9fb9..d16eaf9 100644 --- a/src/wrapper/signature_wrapper.cpp +++ b/src/wrapper/signature_wrapper.cpp @@ -4,7 +4,10 @@ signature_wrapper::~signature_wrapper() { - git_signature_free(p_resource); + if (m_ownership) + { + git_signature_free(p_resource); + } p_resource=nullptr; } @@ -35,12 +38,14 @@ 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 author; - author.p_resource = const_cast(git_commit_committer(cw)); - return author; + 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 1c73c1e..2ebc861 100644 --- a/src/wrapper/signature_wrapper.hpp +++ b/src/wrapper/signature_wrapper.hpp @@ -31,4 +31,5 @@ class signature_wrapper : public wrapper_base 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..6f2d383 --- /dev/null +++ b/test/test_log.py @@ -0,0 +1,36 @@ +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 From 1b0264100ac283de2b1eccc82f366e94827d5e0d Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 27 Aug 2025 14:07:33 +0200 Subject: [PATCH 4/6] free the walker --- src/subcommand/log_subcommand.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/subcommand/log_subcommand.cpp b/src/subcommand/log_subcommand.cpp index ab574f8..5e61e1b 100644 --- a/src/subcommand/log_subcommand.cpp +++ b/src/subcommand/log_subcommand.cpp @@ -94,4 +94,6 @@ void log_subcommand::run() commit_wrapper commit = repo.find_commit(commit_oid); print_commit(commit, m_format_flag); } + + git_revwalk_free(walker); } From bf9dbf8c5a7af555fc44c086a91803a989493649 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 27 Aug 2025 15:27:04 +0200 Subject: [PATCH 5/6] add --max-count --- src/subcommand/log_subcommand.cpp | 6 ++++-- src/subcommand/log_subcommand.hpp | 4 +++- test/test_log.py | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/subcommand/log_subcommand.cpp b/src/subcommand/log_subcommand.cpp index 5e61e1b..688a002 100644 --- a/src/subcommand/log_subcommand.cpp +++ b/src/subcommand/log_subcommand.cpp @@ -13,7 +13,7 @@ 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_flag("-n,--max-count", m_max_count_flag, "Limit the output to commits."); + 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(); }); @@ -88,11 +88,13 @@ void log_subcommand::run() 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)) + while (!git_revwalk_next(&commit_oid, walker) && i +#include +#include #include "../utils/common.hpp" @@ -14,6 +16,6 @@ class log_subcommand private: std::string m_format_flag; - // int m_max_count_flag; + int m_max_count_flag=std::numeric_limits::max(); // bool m_oneline_flag = false; }; diff --git a/test/test_log.py b/test/test_log.py index 6f2d383..659c7a4 100644 --- a/test/test_log.py +++ b/test/test_log.py @@ -34,3 +34,21 @@ def test_log(xtl_clone, git_config, git2cpp_path, tmp_path, monkeypatch, format_ 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 From 4dca90d32896ec40d6bf5863d269068ea1d11800 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 27 Aug 2025 16:57:06 +0200 Subject: [PATCH 6/6] address review comment --- src/subcommand/log_subcommand.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subcommand/log_subcommand.cpp b/src/subcommand/log_subcommand.cpp index 688a002..c25e270 100644 --- a/src/subcommand/log_subcommand.cpp +++ b/src/subcommand/log_subcommand.cpp @@ -94,7 +94,7 @@ void log_subcommand::run() { commit_wrapper commit = repo.find_commit(commit_oid); print_commit(commit, m_format_flag); - i+=1; + ++i; } git_revwalk_free(walker);