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