Skip to content

Commit 4ded7d1

Browse files
authored
Add log subcommand (#38)
add log subcommand
1 parent 9351a2f commit 4ded7d1

File tree

9 files changed

+231
-1
lines changed

9 files changed

+231
-1
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ set(GIT2CPP_SRC
5151
${GIT2CPP_SOURCE_DIR}/subcommand/commit_subcommand.hpp
5252
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.cpp
5353
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp
54+
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.cpp
55+
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.hpp
5456
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.cpp
5557
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.hpp
5658
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp

src/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "subcommand/clone_subcommand.hpp"
1111
#include "subcommand/commit_subcommand.hpp"
1212
#include "subcommand/init_subcommand.hpp"
13+
#include "subcommand/log_subcommand.hpp"
1314
#include "subcommand/reset_subcommand.hpp"
1415
#include "subcommand/status_subcommand.hpp"
1516

@@ -33,6 +34,7 @@ int main(int argc, char** argv)
3334
clone_subcommand clone(lg2_obj, app);
3435
commit_subcommand commit(lg2_obj, app);
3536
reset_subcommand reset(lg2_obj, app);
37+
log_subcommand log(lg2_obj, app);
3638

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

src/subcommand/log_subcommand.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include <format>
2+
#include <git2.h>
3+
#include <git2/revwalk.h>
4+
#include <git2/types.h>
5+
#include <string_view>
6+
7+
#include "log_subcommand.hpp"
8+
#include "../wrapper/repository_wrapper.hpp"
9+
#include "../wrapper/commit_wrapper.hpp"
10+
11+
log_subcommand::log_subcommand(const libgit2_object&, CLI::App& app)
12+
{
13+
auto *sub = app.add_subcommand("log", "Shows commit logs");
14+
15+
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");
16+
sub->add_option("-n,--max-count", m_max_count_flag, "Limit the output to <number> commits.");
17+
// sub->add_flag("--oneline", m_oneline_flag, "This is a shorthand for --pretty=oneline --abbrev-commit used together.");
18+
19+
sub->callback([this]() { this->run(); });
20+
};
21+
22+
void print_time(git_time intime, std::string prefix)
23+
{
24+
char sign, out[32];
25+
struct tm *intm;
26+
int offset, hours, minutes;
27+
time_t t;
28+
29+
offset = intime.offset;
30+
if (offset < 0) {
31+
sign = '-';
32+
offset = -offset;
33+
}
34+
else
35+
{
36+
sign = '+';
37+
}
38+
39+
hours = offset / 60;
40+
minutes = offset % 60;
41+
42+
t = (time_t)intime.time + (intime.offset * 60);
43+
44+
intm = gmtime(&t);
45+
strftime(out, sizeof(out), "%a %b %e %T %Y", intm);
46+
47+
std::cout << prefix << out << " " << sign << std::format("{:02d}", hours) << std::format("{:02d}", minutes) <<std::endl;
48+
}
49+
50+
void print_commit(const commit_wrapper& commit, std::string m_format_flag)
51+
{
52+
std::string buf = commit.commit_oid_tostr();
53+
54+
signature_wrapper author = signature_wrapper::get_commit_author(commit);
55+
signature_wrapper committer = signature_wrapper::get_commit_committer(commit);
56+
57+
std::cout << "\033[0;33m" << "commit " << buf << "\033[0m" << std::endl;
58+
if (m_format_flag=="fuller")
59+
{
60+
std::cout << "Author:\t " << author.name() << " " << author.email() << std::endl;
61+
print_time(author.when(), "AuthorDate: ");
62+
std::cout << "Commit:\t " << committer.name() << " " << committer.email() << std::endl;
63+
print_time(committer.when(), "CommitDate: ");
64+
}
65+
else
66+
{
67+
std::cout << "Author:\t" << author.name() << " " << author.email() << std::endl;
68+
if (m_format_flag=="full")
69+
{
70+
std::cout << "Commit:\t" << committer.name() << " " << committer.email() << std::endl;
71+
}
72+
else
73+
{
74+
print_time(author.when(), "Date:\t");
75+
}
76+
}
77+
std::cout << "\n " << git_commit_message(commit) << "\n" << std::endl;
78+
}
79+
80+
void log_subcommand::run()
81+
{
82+
auto directory = get_current_git_path();
83+
auto bare = false;
84+
auto repo = repository_wrapper::init(directory, bare);
85+
// auto branch_name = repo.head().short_name();
86+
87+
git_revwalk* walker;
88+
git_revwalk_new(&walker, repo);
89+
git_revwalk_push_head(walker);
90+
91+
std::size_t i=0;
92+
git_oid commit_oid;
93+
while (!git_revwalk_next(&commit_oid, walker) && i<m_max_count_flag)
94+
{
95+
commit_wrapper commit = repo.find_commit(commit_oid);
96+
print_commit(commit, m_format_flag);
97+
++i;
98+
}
99+
100+
git_revwalk_free(walker);
101+
}

src/subcommand/log_subcommand.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include <CLI/CLI.hpp>
4+
#include <cstddef>
5+
#include <limits>
6+
7+
#include "../utils/common.hpp"
8+
9+
10+
class log_subcommand
11+
{
12+
public:
13+
14+
explicit log_subcommand(const libgit2_object&, CLI::App& app);
15+
void run();
16+
17+
private:
18+
std::string m_format_flag;
19+
int m_max_count_flag=std::numeric_limits<int>::max();
20+
// bool m_oneline_flag = false;
21+
};

src/wrapper/commit_wrapper.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ const git_oid& commit_wrapper::oid() const
2020
{
2121
return *git_commit_id(p_resource);
2222
}
23+
24+
std::string commit_wrapper::commit_oid_tostr() const
25+
{
26+
char buf[GIT_OID_SHA1_HEXSIZE + 1];
27+
return git_oid_tostr(buf, sizeof(buf), &this->oid());
28+
}

src/wrapper/commit_wrapper.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class commit_wrapper : public wrapper_base<git_commit>
1919
operator git_object*() const noexcept;
2020

2121
const git_oid& oid() const;
22+
std::string commit_oid_tostr() const;
2223

2324
private:
2425

src/wrapper/signature_wrapper.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,48 @@
44

55
signature_wrapper::~signature_wrapper()
66
{
7-
git_signature_free(p_resource);
7+
if (m_ownership)
8+
{
9+
git_signature_free(p_resource);
10+
}
811
p_resource=nullptr;
912
}
1013

14+
std::string_view signature_wrapper::name() const
15+
{
16+
return p_resource->name;
17+
}
18+
19+
std::string_view signature_wrapper::email() const
20+
{
21+
return p_resource->email;
22+
}
23+
24+
git_time signature_wrapper::when() const
25+
{
26+
return p_resource->when;
27+
}
28+
1129
signature_wrapper::author_committer_signatures signature_wrapper::get_default_signature_from_env(repository_wrapper& rw)
1230
{
1331
signature_wrapper author;
1432
signature_wrapper committer;
1533
throw_if_error(git_signature_default_from_env(&(author.p_resource), &(committer.p_resource), rw));
1634
return {std::move(author), std::move(committer)};
1735
}
36+
37+
signature_wrapper signature_wrapper::get_commit_author(const commit_wrapper& cw)
38+
{
39+
signature_wrapper author;
40+
author.p_resource = const_cast<git_signature*>(git_commit_author(cw));
41+
author.m_ownership = false;
42+
return author;
43+
}
44+
45+
signature_wrapper signature_wrapper::get_commit_committer(const commit_wrapper& cw)
46+
{
47+
signature_wrapper committer;
48+
committer.p_resource = const_cast<git_signature*>(git_commit_committer(cw));
49+
committer.m_ownership = false;
50+
return committer;
51+
}

src/wrapper/signature_wrapper.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#pragma once
22

33
#include <utility>
4+
#include <string_view>
45

56
#include <git2.h>
67

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

10+
class commit_wrapper;
911
class repository_wrapper;
1012

1113
class signature_wrapper : public wrapper_base<git_signature>
@@ -18,9 +20,16 @@ class signature_wrapper : public wrapper_base<git_signature>
1820
signature_wrapper(signature_wrapper&&) = default;
1921
signature_wrapper& operator=(signature_wrapper&&) = default;
2022

23+
std::string_view name() const;
24+
std::string_view email() const;
25+
git_time when() const;
26+
2127
static author_committer_signatures get_default_signature_from_env(repository_wrapper&);
28+
static signature_wrapper get_commit_author(const commit_wrapper&);
29+
static signature_wrapper get_commit_committer(const commit_wrapper&);
2230

2331
private:
2432

2533
signature_wrapper() = default;
34+
bool m_ownership=true;
2635
};

test/test_log.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import subprocess
2+
3+
import pytest
4+
5+
@pytest.mark.parametrize("format_flag", ["", "--format=full", "--format=fuller"])
6+
def test_log(xtl_clone, git_config, git2cpp_path, tmp_path, monkeypatch, format_flag):
7+
assert (tmp_path / "xtl").exists()
8+
xtl_path = tmp_path / "xtl"
9+
10+
p = xtl_path / "mook_file.txt"
11+
p.write_text('')
12+
13+
cmd_add = [git2cpp_path, 'add', "mook_file.txt"]
14+
p_add = subprocess.run(cmd_add, cwd=xtl_path, text=True)
15+
assert p_add.returncode == 0
16+
17+
cmd_commit = [git2cpp_path, 'commit', "-m", "test commit"]
18+
p_commit = subprocess.run(cmd_commit, cwd=xtl_path, text=True)
19+
assert p_commit.returncode == 0
20+
21+
cmd_log = [git2cpp_path, 'log']
22+
if format_flag != "":
23+
cmd_log.append(format_flag)
24+
p_log = subprocess.run(cmd_log, capture_output=True, cwd=xtl_path, text=True)
25+
assert p_log.returncode == 0
26+
assert "Jane Doe" in p_log.stdout
27+
assert "test commit" in p_log.stdout
28+
29+
if format_flag == "":
30+
assert "Commit" not in p_log.stdout
31+
else:
32+
assert "Commit" in p_log.stdout
33+
if format_flag == "--format=full":
34+
assert "Date" not in p_log.stdout
35+
else:
36+
assert "CommitDate" in p_log.stdout
37+
38+
39+
@pytest.mark.parametrize("max_count_flag", ["", "-n", "--max-count"])
40+
def test_max_count(xtl_clone, git_config, git2cpp_path, tmp_path, monkeypatch, max_count_flag):
41+
assert (tmp_path / "xtl").exists()
42+
xtl_path = tmp_path / "xtl"
43+
44+
cmd_log = [git2cpp_path, 'log']
45+
if max_count_flag != "":
46+
cmd_log.append(max_count_flag)
47+
cmd_log.append("2")
48+
p_log = subprocess.run(cmd_log, capture_output=True, cwd=xtl_path, text=True)
49+
assert p_log.returncode == 0
50+
51+
if max_count_flag == "":
52+
assert p_log.stdout.count("Author") > 2
53+
else:
54+
assert p_log.stdout.count("Author") == 2

0 commit comments

Comments
 (0)