Skip to content

Commit f9af969

Browse files
authored
feat: add status subcommand (#10)
* refactoring * refactoring v2 * add short flag * address review comments * add test * switch to enum * remove headers in short format * edit test * fix typo
1 parent e735be7 commit f9af969

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1420
-82
lines changed

src/main.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@
22
#include <git2.h> // For version number only
33
#include <iostream>
44

5-
#include "git_exception.hpp"
5+
#include "src/utils/git_exception.hpp"
66
#include "version.hpp"
77
#include "subcommand/init_subcommand.hpp"
8+
#include "subcommand/status_subcommand.hpp"
89

910
int main(int argc, char** argv)
1011
{
1112
int exitCode = 0;
1213
try
1314
{
15+
const libgit2_object lg2_obj;
1416
CLI::App app{"Git using C++ wrapper of libgit2"};
1517

1618
// Top-level command options.
1719
auto version = app.add_flag("-v,--version", "Show version");
1820

1921
// Sub commands
20-
InitSubcommand init(app);
22+
init_subcommand init(lg2_obj, app);
23+
status_subcommand status(lg2_obj, app);
2124

2225
app.parse(argc, argv);
2326

src/meson.build

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
subdir('subcommand')
2+
subdir('utils')
23
subdir('wrapper')
34

45
src_files = files([
5-
'git_exception.cpp',
66
'main.cpp'
7-
]) + subcommand_files + wrapper_files
7+
]) + subcommand_files + utils_files + wrapper_files

src/subcommand/base_subcommand.hpp

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/subcommand/init_subcommand.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#include <filesystem>
1+
// #include <filesystem>
22
#include "init_subcommand.hpp"
3-
#include "../wrapper/repository_wrapper.hpp"
3+
#include "src/wrapper/repository_wrapper.hpp"
44

5-
InitSubcommand::InitSubcommand(CLI::App& app)
5+
init_subcommand::init_subcommand(const libgit2_object&, CLI::App& app)
66
{
77
auto *sub = app.add_subcommand("init", "Explanation of init here");
88

@@ -11,13 +11,12 @@ InitSubcommand::InitSubcommand(CLI::App& app)
1111
// If directory not specified, uses cwd.
1212
sub->add_option("directory", directory, "info about directory arg")
1313
->check(CLI::ExistingDirectory | CLI::NonexistentPath)
14-
->default_val(std::filesystem::current_path());
14+
->default_val(get_current_git_path());
1515

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

19-
void InitSubcommand::run()
19+
void init_subcommand::run()
2020
{
21-
RepositoryWrapper repo;
22-
repo.init(directory, bare);
21+
repository_wrapper::init(directory, bare);
2322
}

src/subcommand/init_subcommand.hpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
#pragma once
22

33
#include <string>
4-
#include "base_subcommand.hpp"
54

6-
class InitSubcommand : public BaseSubcommand
5+
#include <CLI/CLI.hpp>
6+
7+
#include "../utils/common.hpp"
8+
9+
class init_subcommand
710
{
811
public:
9-
InitSubcommand(CLI::App& app);
12+
13+
explicit init_subcommand(const libgit2_object&, CLI::App& app);
1014
void run();
1115

1216
private:

src/subcommand/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
subcommand_files = files([
22
'init_subcommand.cpp',
3+
'status_subcommand.cpp',
34
])

src/subcommand/status_subcommand.cpp

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#include <iostream>
2+
#include <ostream>
3+
#include <string>
4+
5+
#include <git2.h>
6+
7+
#include "status_subcommand.hpp"
8+
#include "../wrapper/status_wrapper.hpp"
9+
10+
status_subcommand::status_subcommand(const libgit2_object&, CLI::App& app)
11+
{
12+
auto *sub = app.add_subcommand("status", "Show modified files in working directory, staged for your next commit");
13+
// Displays paths that have differences between the index file and the current HEAD commit,
14+
// paths that have differences between the working tree and the index file, and paths in the
15+
// working tree that are not tracked by Git (and are not ignored by gitignore[5]).
16+
// The first are what you would commit by running git commit;
17+
// the second and third are what you could commit by running git add before running git commit.
18+
19+
sub->add_flag("-s,--short", short_flag, "Give the output in the short-format.");
20+
sub->add_flag("--long", long_flag, "Give the output in the long-format. This is the default.");
21+
// sub->add_flag("--porcelain[=<version>]", porcelain, "Give the output in an easy-to-parse format for scripts.
22+
// This is similar to the short output, but will remain stable across Git versions and regardless of user configuration.
23+
// See below for details. The version parameter is used to specify the format version. This is optional and defaults
24+
// to the original version v1 format.");
25+
26+
sub->callback([this]() { this->run(); });
27+
};
28+
29+
const std::string untracked_header = "Untracked files:\n";
30+
// "Untracked files:\n (use \"git add <file>...\" to include in what will be committed)";
31+
const std::string tobecommited_header = "Changes to be committed:\n";
32+
// "Changes to be committed:\n (use \"git reset HEAD <file>...\" to unstage)";
33+
const std::string ignored_header = "Ignored files:\n";
34+
// "Ignored files:\n (use \"git add -f <file>...\" to include in what will be committed)"
35+
const std::string notstagged_header = "Changes not staged for commit:\n";
36+
// "Changes not staged for commit:\n (use \"git add%s <file>...\" to update what will be committed)\n (use \"git checkout -- <file>...\" to discard changes in working directory)"
37+
const std::string nothingtocommit_message = "No changes added to commit";
38+
// "No changes added to commit (use \"git add\" and/or \"git commit -a\")"
39+
40+
struct status_messages
41+
{
42+
std::string short_mod;
43+
std::string long_mod;
44+
};
45+
46+
const std::map<git_status_t, status_messages> status_msg_map = //TODO : check spaces in short_mod
47+
{
48+
{ GIT_STATUS_CURRENT, {"", ""} },
49+
{ GIT_STATUS_INDEX_NEW, {"A ", "\t new file:"} },
50+
{ GIT_STATUS_INDEX_MODIFIED, {"M ", "\t modified:"} },
51+
{ GIT_STATUS_INDEX_DELETED, {"D ", "\t deleted:"} },
52+
{ GIT_STATUS_INDEX_RENAMED, {"R ", "\t renamed:"} },
53+
{ GIT_STATUS_INDEX_TYPECHANGE, {"T ", "\t typechange:"} },
54+
{ GIT_STATUS_WT_NEW, {"?? ", ""} },
55+
{ GIT_STATUS_WT_MODIFIED, {" M " , "\t modified:"} },
56+
{ GIT_STATUS_WT_DELETED, {" D ", "\t deleted:"} },
57+
{ GIT_STATUS_WT_TYPECHANGE, {" T ", "\t typechange:"} },
58+
{ GIT_STATUS_WT_RENAMED, {" R ", "\t renamed:"} },
59+
{ GIT_STATUS_WT_UNREADABLE, {"", ""} },
60+
{ GIT_STATUS_IGNORED, {"!! ", ""} },
61+
{ GIT_STATUS_CONFLICTED, {"", ""} },
62+
};
63+
64+
enum class output_format
65+
{
66+
DEFAULT = 0,
67+
LONG = 1,
68+
SHORT = 2
69+
};
70+
71+
void print_entries(git_status_t status, status_list_wrapper& sl, bool head_selector, output_format of) // TODO: add different mods
72+
{
73+
const auto& entry_list = sl.get_entry_list(status);
74+
if (!entry_list.empty())
75+
{
76+
for (auto* entry : entry_list)
77+
{
78+
if ((of == output_format::DEFAULT) || (of == output_format::LONG))
79+
{
80+
std::cout << status_msg_map.at(status).long_mod << "\t";
81+
}
82+
else if (of == output_format::SHORT)
83+
{
84+
std::cout << status_msg_map.at(status).short_mod;
85+
}
86+
87+
git_diff_delta* diff_delta;
88+
if (head_selector)
89+
{
90+
diff_delta = entry->head_to_index;
91+
}
92+
else
93+
{
94+
diff_delta = entry->index_to_workdir;
95+
}
96+
const char* old_path = diff_delta->old_file.path;
97+
const char* new_path = diff_delta->new_file.path;
98+
if (old_path && new_path && std::strcmp(old_path, new_path))
99+
{
100+
std::cout << old_path << " -> " << new_path << std::endl;
101+
}
102+
else
103+
{
104+
if (old_path)
105+
{
106+
std::cout << old_path << std::endl;
107+
}
108+
else
109+
{
110+
std::cout << new_path << std::endl;
111+
}
112+
}
113+
}
114+
}
115+
else
116+
{}
117+
}
118+
119+
void status_subcommand::run()
120+
{
121+
auto directory = get_current_git_path();
122+
auto bare = false;
123+
auto repo = repository_wrapper::init(directory, bare);
124+
auto sl = status_list_wrapper::status_list(repo);
125+
126+
// TODO: add branch info
127+
128+
output_format of = output_format::DEFAULT;
129+
if (short_flag)
130+
{
131+
of = output_format::SHORT;
132+
}
133+
if (long_flag)
134+
{
135+
of = output_format::LONG;
136+
}
137+
// else if (porcelain_format)
138+
// {
139+
// output_format = 3;
140+
// }
141+
142+
bool is_long;
143+
is_long = ((of == output_format::DEFAULT) || (of == output_format::LONG));
144+
if (sl.has_tobecommited_header())
145+
{
146+
if (is_long)
147+
{
148+
std::cout << tobecommited_header << std::endl;
149+
}
150+
print_entries(GIT_STATUS_INDEX_NEW, sl, true, of);
151+
print_entries(GIT_STATUS_INDEX_MODIFIED, sl, true, of);
152+
print_entries(GIT_STATUS_INDEX_DELETED, sl, true, of);
153+
print_entries(GIT_STATUS_INDEX_RENAMED, sl, true, of);
154+
print_entries(GIT_STATUS_INDEX_TYPECHANGE, sl, true, of);
155+
if (is_long)
156+
{
157+
std::cout << std::endl;
158+
}
159+
}
160+
161+
if (sl.has_notstagged_header())
162+
{
163+
if (is_long)
164+
{
165+
std::cout << notstagged_header << std::endl;
166+
}
167+
print_entries(GIT_STATUS_WT_MODIFIED, sl, false, of);
168+
print_entries(GIT_STATUS_WT_DELETED, sl, false, of);
169+
print_entries(GIT_STATUS_WT_TYPECHANGE, sl, false, of);
170+
print_entries(GIT_STATUS_WT_RENAMED, sl, false, of);
171+
if (is_long)
172+
{
173+
std::cout << std::endl;
174+
}
175+
}
176+
177+
if (sl.has_untracked_header())
178+
{
179+
if (is_long)
180+
{
181+
std::cout << untracked_header << std::endl;
182+
}
183+
print_entries(GIT_STATUS_WT_NEW, sl, false, of);
184+
if (is_long)
185+
{
186+
std::cout << std::endl;
187+
}
188+
}
189+
190+
if (sl.has_ignored_header())
191+
{
192+
if (is_long)
193+
{
194+
std::cout << ignored_header << std::endl;
195+
}
196+
print_entries(GIT_STATUS_IGNORED, sl, false, of);
197+
if (is_long)
198+
{
199+
std::cout << std::endl;
200+
}
201+
}
202+
}

src/subcommand/status_subcommand.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <CLI/CLI.hpp>
4+
5+
#include "../utils/common.hpp"
6+
7+
class status_subcommand
8+
{
9+
public:
10+
11+
explicit status_subcommand(const libgit2_object&, CLI::App& app);
12+
void run();
13+
14+
private:
15+
bool short_flag = false;
16+
bool long_flag = false;
17+
};

src/utils/common.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <filesystem>
2+
3+
#include <git2.h>
4+
5+
#include "common.hpp"
6+
7+
libgit2_object::libgit2_object()
8+
{
9+
git_libgit2_init();
10+
}
11+
12+
libgit2_object::~libgit2_object()
13+
{
14+
git_libgit2_shutdown();
15+
}
16+
17+
std::string get_current_git_path()
18+
{
19+
return std::filesystem::current_path(); // TODO: make sure that it goes to the root
20+
}
21+
22+
// // If directory not specified, uses cwd.
23+
// sub->add_option("directory", directory, "info about directory arg")
24+
// ->check(CLI::ExistingDirectory | CLI::NonexistentPath)
25+
// ->default_val(std::filesystem::current_path());

0 commit comments

Comments
 (0)