Skip to content

Commit 71f078a

Browse files
committed
Add tag subcommand
1 parent 4bd9b8e commit 71f078a

16 files changed

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

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);

0 commit comments

Comments
 (0)