Skip to content

Commit 79ac27f

Browse files
committed
Add tag subcommand
1 parent 4bd9b8e commit 79ac27f

16 files changed

+720
-1
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ set(GIT2CPP_SRC
8282
${GIT2CPP_SOURCE_DIR}/subcommand/stash_subcommand.hpp
8383
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
8484
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.hpp
85+
${GIT2CPP_SOURCE_DIR}/subcommand/tag_subcommand.cpp
86+
${GIT2CPP_SOURCE_DIR}/subcommand/tag_subcommand.hpp
8587
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.cpp
8688
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.hpp
8789
${GIT2CPP_SOURCE_DIR}/utils/common.cpp
@@ -126,6 +128,8 @@ set(GIT2CPP_SRC
126128
${GIT2CPP_SOURCE_DIR}/wrapper/signature_wrapper.hpp
127129
${GIT2CPP_SOURCE_DIR}/wrapper/status_wrapper.cpp
128130
${GIT2CPP_SOURCE_DIR}/wrapper/status_wrapper.hpp
131+
${GIT2CPP_SOURCE_DIR}/wrapper/tag_wrapper.cpp
132+
${GIT2CPP_SOURCE_DIR}/wrapper/tag_wrapper.hpp
129133
${GIT2CPP_SOURCE_DIR}/wrapper/tree_wrapper.cpp
130134
${GIT2CPP_SOURCE_DIR}/wrapper/tree_wrapper.hpp
131135
${GIT2CPP_SOURCE_DIR}/wrapper/wrapper_base.hpp

src/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "subcommand/reset_subcommand.hpp"
2424
#include "subcommand/stash_subcommand.hpp"
2525
#include "subcommand/status_subcommand.hpp"
26+
#include "subcommand/tag_subcommand.hpp"
2627
#include "subcommand/revparse_subcommand.hpp"
2728
#include "subcommand/revlist_subcommand.hpp"
2829
#include "subcommand/rm_subcommand.hpp"
@@ -60,6 +61,7 @@ int main(int argc, char** argv)
6061
revparse_subcommand revparse(lg2_obj, app);
6162
rm_subcommand rm(lg2_obj, app);
6263
stash_subcommand stash(lg2_obj, app);
64+
tag_subcommand tag(lg2_obj, app);
6365

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

src/subcommand/log_subcommand.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void print_commit(const commit_wrapper& commit, std::string m_format_flag)
7878
print_time(author.when(), "Date:\t");
7979
}
8080
}
81-
std::cout << "\n " << git_commit_message(commit) << "\n" << std::endl;
81+
std::cout << "\n " << commit.message() << "\n" << std::endl;
8282
}
8383

8484
void log_subcommand::run()

src/subcommand/tag_subcommand.cpp

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
#include <git2.h>
2+
3+
#include "../subcommand/tag_subcommand.hpp"
4+
#include "../wrapper/commit_wrapper.hpp"
5+
#include "../wrapper/tag_wrapper.hpp"
6+
7+
tag_subcommand::tag_subcommand(const libgit2_object&, CLI::App& app)
8+
{
9+
auto* sub = app.add_subcommand("tag", "Create, list, delete or verify tags");
10+
11+
sub->add_flag("-l,--list", m_list_flag, "List tags. With optional <pattern>.");
12+
sub->add_flag("-f,--force", m_force_flag, "Replace an existing tag with the given name (instead of failing)");
13+
sub->add_option("-d,--delete", m_delete, "Delete existing tags with the given names.");
14+
sub->add_option("-n", m_num_lines, "<num> specifies how many lines from the annotation, if any, are printed when using -l. Implies --list.");
15+
sub->add_option("-m,--message", m_message, "Tag message for annotated tags");
16+
sub->add_option("<tagname>", m_tag_name, "Tag name");
17+
sub->add_option("<commit>", m_target, "Target commit (defaults to HEAD)");
18+
19+
sub->callback([this]() { this->run(); });
20+
}
21+
22+
// Tag listing: Print individual message lines
23+
void print_list_lines(const std::string& message, int num_lines)
24+
{
25+
if (message.empty())
26+
{
27+
return;
28+
}
29+
30+
size_t pos = 0;
31+
int num = num_lines - 1; // TODO: check with git re. "- 1"
32+
33+
/** first line - headline */
34+
size_t newline_pos = message.find('\n', pos);
35+
if (newline_pos != std::string::npos)
36+
{
37+
std::cout << message.substr(pos, newline_pos - pos);
38+
pos = newline_pos;
39+
}
40+
else
41+
{
42+
std::cout << message << std::endl;
43+
return;
44+
}
45+
46+
/** skip over new lines */
47+
while (pos < message.length() && message[pos] == '\n')
48+
{
49+
pos++;
50+
}
51+
52+
std::cout << std::endl;
53+
54+
/** print just headline? */
55+
if (num == 0)
56+
{
57+
return;
58+
}
59+
if (pos < message.length() && pos + 1 < message.length())
60+
{
61+
std::cout << std::endl;
62+
}
63+
64+
/** print individual commit/tag lines */
65+
while (pos < message.length() && num >= 2)
66+
{
67+
std::cout << " ";
68+
69+
newline_pos = message.find('\n', pos);
70+
if (newline_pos != std::string::npos)
71+
{
72+
std::cout << message.substr(pos, newline_pos - pos);
73+
pos = newline_pos;
74+
}
75+
else
76+
{
77+
std::cout << message.substr(pos);
78+
break;
79+
}
80+
81+
// Handle consecutive newlines
82+
if (pos + 1 < message.length() &&
83+
message[pos] == '\n' && message[pos + 1] == '\n')
84+
{
85+
num--;
86+
std::cout << std::endl;
87+
}
88+
89+
while (pos < message.length() && message[pos] == '\n')
90+
{
91+
pos++;
92+
}
93+
94+
std::cout << std::endl;
95+
num--;
96+
}
97+
}
98+
99+
// Tag listing: Print an actual tag object
100+
void print_tag(git_tag* tag, int num_lines) // TODO: switch to tag_wrapper
101+
{
102+
std::cout << std::left << std::setw(16) << git_tag_name(tag); // TODO: replace with tag.name()
103+
104+
if (num_lines)
105+
{
106+
std::string msg = git_tag_message(tag); // TODO: replace with tag.message()
107+
if (!msg.empty())
108+
{
109+
print_list_lines(msg, num_lines);
110+
}
111+
else
112+
{
113+
std::cout << std::endl;
114+
}
115+
}
116+
else
117+
{
118+
std::cout << std::endl;
119+
}
120+
}
121+
122+
// Tag listing: Print a commit (target of a lightweight tag)
123+
void print_commit(git_commit* commit, std::string name, int num_lines) // TODO: switch to commit_wrapper
124+
{
125+
std::cout << std::left << std::setw(16) << name;
126+
127+
if (num_lines)
128+
{
129+
std::string msg = git_commit_message(commit); // TODO: replace with commit.message()
130+
if (!msg.empty())
131+
{
132+
print_list_lines(msg, num_lines);
133+
}
134+
else
135+
{
136+
std::cout <<std::endl;
137+
}
138+
}
139+
else
140+
{
141+
std::cout <<std::endl;
142+
}
143+
}
144+
145+
// Tag listing: Lookup tags based on ref name and dispatch to print
146+
void each_tag(repository_wrapper& repo, const std::string& name, int num_lines)
147+
{
148+
auto obj = repo.revparse_single(name);
149+
150+
if (obj.has_value())
151+
{
152+
switch (git_object_type(obj.value()))
153+
{
154+
case GIT_OBJECT_TAG:
155+
print_tag(obj.value(), num_lines);
156+
break;
157+
case GIT_OBJECT_COMMIT:
158+
print_commit(obj.value(), name, num_lines);
159+
break;
160+
default:
161+
std::cout << name << std::endl;
162+
}
163+
}
164+
else
165+
{
166+
std::cout << name << std::endl;
167+
}
168+
}
169+
170+
void tag_subcommand::list_tags(repository_wrapper& repo)
171+
{
172+
std::string pattern = m_tag_name.empty() ? "*" : m_tag_name;
173+
auto tag_names = repo.tag_list_match(pattern);
174+
175+
for (size_t i = 0; i < tag_names.size(); i++)
176+
{
177+
each_tag(repo, tag_names[i], m_num_lines);
178+
}
179+
}
180+
181+
void tag_subcommand::delete_tag(repository_wrapper& repo)
182+
{
183+
if (m_delete.empty())
184+
{
185+
throw git_exception("Name required for tag deletion.", git2cpp_error_code::GENERIC_ERROR);
186+
}
187+
188+
auto obj = repo.revparse_single(m_delete);
189+
if (!obj.has_value())
190+
{
191+
throw git_exception("error: tag '" + m_delete + "' not found.", git2cpp_error_code::GENERIC_ERROR);
192+
}
193+
194+
git_buf abbrev_oid = GIT_BUF_INIT;
195+
throw_if_error(git_object_short_id(&abbrev_oid, obj.value()));
196+
197+
std::string oid_str(abbrev_oid.ptr);
198+
git_buf_dispose(&abbrev_oid);
199+
200+
throw_if_error(git_tag_delete(repo, m_delete.c_str()));
201+
std::cout << "Deleted tag '" << m_delete << "' (was " << oid_str << ")" << std::endl;
202+
}
203+
204+
void tag_subcommand::create_lightweight_tag(repository_wrapper& repo)
205+
{
206+
if (m_tag_name.empty())
207+
{
208+
throw git_exception("Tag name required", git2cpp_error_code::GENERIC_ERROR);
209+
}
210+
211+
std::string target = m_target.empty() ? "HEAD" : m_target;
212+
213+
auto target_obj = repo.revparse_single(target);
214+
if (!target_obj.has_value())
215+
{
216+
throw git_exception("Unable to resolve target: " + target, git2cpp_error_code::GENERIC_ERROR);
217+
}
218+
219+
git_oid oid;
220+
size_t force = m_force_flag ? 1 : 0;
221+
int error = git_tag_create_lightweight(&oid, repo, m_tag_name.c_str(), target_obj.value(), force);
222+
223+
if (error < 0)
224+
{
225+
if (error == GIT_EEXISTS)
226+
{
227+
throw git_exception("tag '" + m_tag_name + "' already exists", git2cpp_error_code::FILESYSTEM_ERROR);
228+
}
229+
throw git_exception("Unable to create lightweight tag", error);
230+
}
231+
}
232+
233+
void tag_subcommand::create_tag(repository_wrapper& repo)
234+
{
235+
if (m_tag_name.empty())
236+
{
237+
throw git_exception("Tag name required", git2cpp_error_code::GENERIC_ERROR);
238+
}
239+
240+
if (m_message.empty())
241+
{
242+
throw git_exception("Message required for annotated tag (use -m)", git2cpp_error_code::GENERIC_ERROR);
243+
}
244+
245+
std::string target = m_target.empty() ? "HEAD" : m_target;
246+
247+
auto target_obj = repo.revparse_single(target);
248+
if (!target_obj.has_value())
249+
{
250+
throw git_exception("Unable to resolve target: " + target, git2cpp_error_code::GENERIC_ERROR);
251+
}
252+
253+
auto tagger = signature_wrapper::get_default_signature_from_env(repo);
254+
255+
git_oid oid;
256+
size_t force = m_force_flag ? 1 : 0;
257+
int error = git_tag_create(&oid, repo, m_tag_name.c_str(), target_obj.value(), tagger.first, m_message.c_str(), force);
258+
259+
if (error < 0)
260+
{
261+
if (error == GIT_EEXISTS)
262+
{
263+
throw git_exception("tag '" + m_tag_name + "' already exists", git2cpp_error_code::FILESYSTEM_ERROR);
264+
}
265+
throw git_exception("Unable to create annotated tag", error);
266+
}
267+
}
268+
269+
void tag_subcommand::run()
270+
{
271+
auto directory = get_current_git_path();
272+
auto repo = repository_wrapper::open(directory);
273+
274+
if (!m_delete.empty())
275+
{
276+
delete_tag(repo);
277+
}
278+
else if (m_list_flag || (m_tag_name.empty() && m_message.empty()))
279+
{
280+
list_tags(repo);
281+
}
282+
else if (!m_message.empty())
283+
{
284+
create_tag(repo);
285+
}
286+
else if (!m_tag_name.empty())
287+
{
288+
create_lightweight_tag(repo);
289+
}
290+
else
291+
{
292+
list_tags(repo);
293+
}
294+
295+
}

src/subcommand/tag_subcommand.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
3+
#include <CLI/CLI.hpp>
4+
5+
#include "../utils/common.hpp"
6+
#include "../wrapper/repository_wrapper.hpp"
7+
8+
class tag_subcommand
9+
{
10+
public:
11+
12+
explicit tag_subcommand(const libgit2_object&, CLI::App& app);
13+
14+
void run();
15+
16+
private:
17+
18+
void list_tags(repository_wrapper& repo);
19+
void delete_tag(repository_wrapper& repo);
20+
void create_lightweight_tag(repository_wrapper& repo);
21+
void create_tag(repository_wrapper& repo);
22+
23+
std::string m_delete;
24+
std::string m_message;
25+
std::string m_tag_name;
26+
std::string m_target;
27+
bool m_list_flag = false;
28+
bool m_force_flag = false;
29+
int m_num_lines = 0;
30+
};

src/utils/common.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include <unistd.h>
66
#include <map>
77

8+
#include <git2.h>
9+
810
#include "common.hpp"
911
#include "git_exception.hpp"
1012

@@ -103,6 +105,11 @@ void git_strarray_wrapper::init_str_array()
103105
}
104106
}
105107

108+
size_t git_strarray_wrapper::size()
109+
{
110+
return m_patterns.size();
111+
}
112+
106113
std::string read_file(const std::string& path)
107114
{
108115
std::ifstream file(path, std::ios::binary);

src/utils/common.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class git_strarray_wrapper
5757

5858
operator git_strarray*();
5959

60+
size_t size();
61+
6062
private:
6163
std::vector<std::string> m_patterns;
6264
git_strarray m_array;

0 commit comments

Comments
 (0)