1 /*
2  * libgit2 "tag" example - shows how to list, create and delete tags
3  *
4  * Written by the libgit2 contributors
5  *
6  * To the extent possible under law, the author(s) have dedicated all copyright
7  * and related and neighboring rights to this software to the public domain
8  * worldwide. This software is distributed without any warranty.
9  *
10  * You should have received a copy of the CC0 Public Domain Dedication along
11  * with this software. If not, see
12  * <https://creativecommons.org/publicdomain/zero/1.0/>.
13  */
14 /**
15  * The following example partially reimplements the `git tag` command
16  * and some of its options.
17  *
18  * These commands should work:
19  *
20  * - Tag name listing (`tag`)
21  * - Filtered tag listing with messages (`tag -n3 -l "v0.1*"`)
22  * - Lightweight tag creation (`tag test v0.18.0`)
23  * - Tag creation (`tag -a -m "Test message" test v0.18.0`)
24  * - Tag deletion (`tag -d test`)
25  *
26  * The command line parsing logic is simplified and doesn't handle
27  * all of the use cases.
28  *
29  * License: $(LINK2 https://creativecommons.org/publicdomain/zero/1.0/, CC0 1.0 Universal)
30  */
31 module libgit2.example.tag;
32 
33 
34 private static import core.stdc.stdio;
35 private static import core.stdc.stdlib;
36 private static import core.stdc.string;
37 private static import libgit2.buffer;
38 private static import libgit2.commit;
39 private static import libgit2.example.args;
40 private static import libgit2.example.common;
41 private static import libgit2.object;
42 private static import libgit2.oid;
43 private static import libgit2.revparse;
44 private static import libgit2.signature;
45 private static import libgit2.strarray;
46 private static import libgit2.tag;
47 private static import libgit2.types;
48 
49 /**
50  * tag_options represents the parsed command line options
51  */
52 extern (C)
53 public struct tag_options
54 {
55 	const (char)* message;
56 	const (char)* pattern;
57 	const (char)* tag_name;
58 	const (char)* target;
59 	int num_lines;
60 	int force;
61 }
62 
63 /**
64  * tag_state represents the current program state for dragging around
65  */
66 extern (C)
67 public struct tag_state
68 {
69 	libgit2.types.git_repository* repo;
70 	.tag_options* opts;
71 }
72 
73 /**
74  * An action to execute based on the command line arguments
75  */
76 public alias tag_action = nothrow @nogc void function(.tag_state* state);
77 
78 nothrow @nogc
79 private void check(int result, const (char)* message)
80 
81 	in
82 	{
83 	}
84 
85 	do
86 	{
87 		if (result) {
88 			libgit2.example.common.fatal(message, null);
89 		}
90 	}
91 
92 /**
93  * Tag listing: Print individual message lines
94  */
95 nothrow @nogc
96 private void print_list_lines(const (char)* message, const (.tag_state)* state)
97 
98 	in
99 	{
100 	}
101 
102 	do
103 	{
104 		const (char)* msg = message;
105 		int num = state.opts.num_lines - 1;
106 
107 		if (msg == null) {
108 			return;
109 		}
110 
111 		/** first line - headline */
112 		while ((*msg) && (*msg != '\n')) {
113 			core.stdc.stdio.printf("%c", *msg++);
114 		}
115 
116 		/** skip over new lines */
117 		while ((*msg) && (*msg == '\n')) {
118 			msg++;
119 		}
120 
121 		core.stdc.stdio.printf("\n");
122 
123 		/** print just headline? */
124 		if (num == 0) {
125 			return;
126 		}
127 
128 		if ((*msg) && (msg[1])) {
129 			core.stdc.stdio.printf("\n");
130 		}
131 
132 		/** print individual commit/tag lines */
133 		while ((*msg) && (num-- >= 2)) {
134 			core.stdc.stdio.printf("    ");
135 
136 			while ((*msg) && (*msg != '\n')) {
137 				core.stdc.stdio.printf("%c", *msg++);
138 			}
139 
140 			/** handle consecutive new lines */
141 			if ((*msg) && (*msg == '\n') && (msg[1] == '\n')) {
142 				num--;
143 				core.stdc.stdio.printf("\n");
144 			}
145 
146 			while ((*msg) && (*msg == '\n')) {
147 				msg++;
148 			}
149 
150 			core.stdc.stdio.printf("\n");
151 		}
152 	}
153 
154 /**
155  * Tag listing: Print an actual tag object
156  */
157 nothrow @nogc
158 private void print_tag(libgit2.types.git_tag* tag, const (.tag_state)* state)
159 
160 	in
161 	{
162 	}
163 
164 	do
165 	{
166 		core.stdc.stdio.printf("%-16s", libgit2.tag.git_tag_name(tag));
167 
168 		if (state.opts.num_lines) {
169 			const (char)* msg = libgit2.tag.git_tag_message(tag);
170 			.print_list_lines(msg, state);
171 		} else {
172 			core.stdc.stdio.printf("\n");
173 		}
174 	}
175 
176 /**
177  * Tag listing: Print a commit (target of a lightweight tag)
178  */
179 nothrow @nogc
180 private void print_commit(libgit2.types.git_commit* commit, const (char)* name, const (.tag_state)* state)
181 
182 	in
183 	{
184 	}
185 
186 	do
187 	{
188 		core.stdc.stdio.printf("%-16s", name);
189 
190 		if (state.opts.num_lines) {
191 			const (char)* msg = libgit2.commit.git_commit_message(commit);
192 			.print_list_lines(msg, state);
193 		} else {
194 			core.stdc.stdio.printf("\n");
195 		}
196 	}
197 
198 /**
199  * Tag listing: Fallback, should not happen
200  */
201 nothrow @nogc
202 private void print_name(const (char)* name)
203 
204 	in
205 	{
206 	}
207 
208 	do
209 	{
210 		core.stdc.stdio.printf("%s\n", name);
211 	}
212 
213 /**
214  * Tag listing: Lookup tags based on ref name and dispatch to print
215  */
216 nothrow @nogc
217 private int each_tag(const (char)* name, .tag_state* state)
218 
219 	in
220 	{
221 	}
222 
223 	do
224 	{
225 		libgit2.types.git_repository* repo = state.repo;
226 		libgit2.types.git_object* obj;
227 
228 		libgit2.example.common.check_lg2(libgit2.revparse.git_revparse_single(&obj, repo, name), "Failed to lookup rev", name);
229 
230 		switch (libgit2.object.git_object_type(obj)) {
231 			case libgit2.types.git_object_t.GIT_OBJECT_TAG:
232 				.print_tag(cast(libgit2.types.git_tag*)(obj), state);
233 
234 				break;
235 
236 			case libgit2.types.git_object_t.GIT_OBJECT_COMMIT:
237 				.print_commit(cast(libgit2.types.git_commit*)(obj), name, state);
238 
239 				break;
240 
241 			default:
242 				.print_name(name);
243 
244 				break;
245 		}
246 
247 		libgit2.object.git_object_free(obj);
248 
249 		return 0;
250 	}
251 
252 nothrow @nogc
253 private void action_list_tags(.tag_state* state)
254 
255 	in
256 	{
257 	}
258 
259 	do
260 	{
261 		const (char)* pattern = state.opts.pattern;
262 		libgit2.strarray.git_strarray tag_names = libgit2.strarray.git_strarray.init;
263 
264 		libgit2.example.common.check_lg2(libgit2.tag.git_tag_list_match(&tag_names, (pattern) ? (pattern) : ("*"), state.repo), "Unable to get list of tags", null);
265 
266 		for (size_t i = 0; i < tag_names.count; i++) {
267 			.each_tag(tag_names.strings[i], state);
268 		}
269 
270 		libgit2.strarray.git_strarray_dispose(&tag_names);
271 	}
272 
273 nothrow @nogc
274 private void action_delete_tag(.tag_state* state)
275 
276 	in
277 	{
278 	}
279 
280 	do
281 	{
282 		.tag_options* opts = state.opts;
283 		libgit2.types.git_object* obj;
284 		libgit2.buffer.git_buf abbrev_oid = libgit2.buffer.git_buf.init;
285 
286 		.check(!opts.tag_name, "Name required");
287 
288 		libgit2.example.common.check_lg2(libgit2.revparse.git_revparse_single(&obj, state.repo, opts.tag_name), "Failed to lookup rev", opts.tag_name);
289 
290 		libgit2.example.common.check_lg2(libgit2.object.git_object_short_id(&abbrev_oid, obj), "Unable to get abbreviated OID", opts.tag_name);
291 
292 		libgit2.example.common.check_lg2(libgit2.tag.git_tag_delete(state.repo, opts.tag_name), "Unable to delete tag", opts.tag_name);
293 
294 		core.stdc.stdio.printf("Deleted tag '%s' (was %s)\n", opts.tag_name, abbrev_oid.ptr_);
295 
296 		libgit2.buffer.git_buf_dispose(&abbrev_oid);
297 		libgit2.object.git_object_free(obj);
298 	}
299 
300 nothrow @nogc
301 private void action_create_lightweight_tag(.tag_state* state)
302 
303 	in
304 	{
305 	}
306 
307 	do
308 	{
309 		libgit2.types.git_repository* repo = state.repo;
310 		.tag_options* opts = state.opts;
311 
312 		.check(!opts.tag_name, "Name required");
313 
314 		if (opts.target == null) {
315 			opts.target = "HEAD";
316 		}
317 
318 		.check(!opts.target, "Target required");
319 
320 		libgit2.types.git_object* target;
321 		libgit2.example.common.check_lg2(libgit2.revparse.git_revparse_single(&target, repo, opts.target), "Unable to resolve spec", opts.target);
322 
323 		libgit2.oid.git_oid oid;
324 		libgit2.example.common.check_lg2(libgit2.tag.git_tag_create_lightweight(&oid, repo, opts.tag_name, target, opts.force), "Unable to create tag", null);
325 
326 		libgit2.object.git_object_free(target);
327 	}
328 
329 nothrow @nogc
330 private void action_create_tag(.tag_state* state)
331 
332 	in
333 	{
334 	}
335 
336 	do
337 	{
338 		libgit2.types.git_repository* repo = state.repo;
339 		.tag_options* opts = state.opts;
340 
341 		.check(!opts.tag_name, "Name required");
342 		.check(!opts.message, "Message required");
343 
344 		if (opts.target == null) {
345 			opts.target = "HEAD";
346 		}
347 
348 		libgit2.types.git_object* target;
349 		libgit2.example.common.check_lg2(libgit2.revparse.git_revparse_single(&target, repo, opts.target), "Unable to resolve spec", opts.target);
350 
351 		libgit2.types.git_signature* tagger;
352 		libgit2.example.common.check_lg2(libgit2.signature.git_signature_default(&tagger, repo), "Unable to create signature", null);
353 
354 		libgit2.oid.git_oid oid;
355 		libgit2.example.common.check_lg2(libgit2.tag.git_tag_create(&oid, repo, opts.tag_name, target, tagger, opts.message, opts.force), "Unable to create tag", null);
356 
357 		libgit2.object.git_object_free(target);
358 		libgit2.signature.git_signature_free(tagger);
359 	}
360 
361 nothrow @nogc
362 private void print_usage()
363 
364 	in
365 	{
366 	}
367 
368 	do
369 	{
370 		core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "usage: see `git help tag`\n");
371 		core.stdc.stdlib.exit(1);
372 	}
373 
374 /**
375  * Parse command line arguments and choose action to run when done
376  */
377 nothrow @nogc
378 private void parse_options(.tag_action* action, .tag_options* opts, int argc, char** argv)
379 
380 	in
381 	{
382 	}
383 
384 	do
385 	{
386 		libgit2.example.args.args_info args = libgit2.example.args.ARGS_INFO_INIT(argc, argv);
387 		*action = &.action_list_tags;
388 
389 		for (args.pos = 1; args.pos < argc; ++args.pos) {
390 			const (char)* curr = argv[args.pos];
391 
392 			if (curr[0] != '-') {
393 				if (opts.tag_name == null) {
394 					opts.tag_name = curr;
395 				} else if (opts.target == null) {
396 					opts.target = curr;
397 				} else {
398 					.print_usage();
399 				}
400 
401 				if (*action != &.action_create_tag) {
402 					*action = &.action_create_lightweight_tag;
403 				}
404 			} else if (!core.stdc..string.strcmp(curr, "-n")) {
405 				opts.num_lines = 1;
406 				*action = &.action_list_tags;
407 			} else if (!core.stdc..string.strcmp(curr, "-a")) {
408 				*action = &.action_create_tag;
409 			} else if (!core.stdc..string.strcmp(curr, "-f")) {
410 				opts.force = 1;
411 			} else if (libgit2.example.args.match_int_arg(&opts.num_lines, &args, "-n", 0)) {
412 				*action = &.action_list_tags;
413 			} else if (libgit2.example.args.match_str_arg(&opts.pattern, &args, "-l")) {
414 				*action = &.action_list_tags;
415 			} else if (libgit2.example.args.match_str_arg(&opts.tag_name, &args, "-d")) {
416 				*action = &.action_delete_tag;
417 			} else if (libgit2.example.args.match_str_arg(&opts.message, &args, "-m")) {
418 				*action = &.action_create_tag;
419 			}
420 		}
421 	}
422 
423 /**
424  * Initialize tag_options struct
425  */
426 nothrow @nogc
427 private void tag_options_init(.tag_options* opts)
428 
429 	in
430 	{
431 	}
432 
433 	do
434 	{
435 		core.stdc..string.memset(opts, 0, (*opts).sizeof);
436 
437 		opts.message = null;
438 		opts.pattern = null;
439 		opts.tag_name = null;
440 		opts.target = null;
441 		opts.num_lines = 0;
442 		opts.force = 0;
443 	}
444 
445 extern (C)
446 nothrow @nogc
447 public int lg2_tag(libgit2.types.git_repository* repo, int argc, char** argv)
448 
449 	in
450 	{
451 	}
452 
453 	do
454 	{
455 		.tag_options opts;
456 		.tag_options_init(&opts);
457 		.tag_action action;
458 		.parse_options(&action, &opts, argc, argv);
459 
460 		.tag_state state =
461 		{
462 			repo: repo,
463 			opts: &opts,
464 		};
465 
466 		action(&state);
467 
468 		return 0;
469 	}