1 /*
2  * libgit2 "describe" example - shows how to describe commits
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.describe;
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.describe;
22 private static import libgit2_d.example.args;
23 private static import libgit2_d.example.common;
24 private static import libgit2_d.revparse;
25 private static import libgit2_d.types;
26 
27 package:
28 
29 /**
30  * The following example partially reimplements the `git describe` command
31  * and some of its options.
32  *
33  * These commands should work:
34  *
35  * - Describe HEAD with default options (`describe`)
36  * - Describe specified revision (`describe master~2`)
37  * - Describe specified revisions (`describe master~2 HEAD~3`)
38  * - Describe HEAD with dirty state suffix (`describe --dirty=*`)
39  * - Describe consider all refs (`describe --all master`)
40  * - Describe consider lightweight tags (`describe --tags temp-tag`)
41  * - Describe show non-default abbreviated size (`describe --abbrev=10`)
42  * - Describe always output the long format if matches a tag (`describe --long v1.0`)
43  * - Describe consider only tags of specified pattern (`describe --match v*-release`)
44  * - Describe show the fallback result (`describe --always`)
45  * - Describe follow only the first parent commit (`describe --first-parent`)
46  *
47  * The command line parsing logic is simplified and doesn't handle
48  * all of the use cases.
49  */
50 
51 /**
52  * describe_options represents the parsed command line options
53  */
54 public struct describe_options
55 {
56 	const (char)** commits;
57 	size_t commit_count;
58 	libgit2_d.describe.git_describe_options describe_options;
59 	libgit2_d.describe.git_describe_format_options format_options;
60 }
61 
62 nothrow @nogc
63 private void opts_add_commit(.describe_options* opts, const (char)* commit)
64 
65 	in
66 	{
67 		assert(opts != null);
68 	}
69 
70 	do
71 	{
72 		size_t sz = ++opts.commit_count * opts.commits[0].sizeof;
73 		opts.commits = cast(const (char)**)(libgit2_d.example.common.xrealloc(cast(void*)(opts.commits), sz));
74 		opts.commits[opts.commit_count - 1] = commit;
75 	}
76 
77 nothrow @nogc
78 private void do_describe_single(libgit2_d.types.git_repository* repo, .describe_options* opts, const (char)* rev)
79 
80 	in
81 	{
82 	}
83 
84 	do
85 	{
86 		libgit2_d.types.git_object* commit;
87 		libgit2_d.describe.git_describe_result* describe_result;
88 
89 		if (rev != null) {
90 			libgit2_d.example.common.check_lg2(libgit2_d.revparse.git_revparse_single(&commit, repo, rev), "Failed to lookup rev", rev);
91 
92 			libgit2_d.example.common.check_lg2(libgit2_d.describe.git_describe_commit(&describe_result, commit, &opts.describe_options), "Failed to describe rev", rev);
93 		} else {
94 			libgit2_d.example.common.check_lg2(libgit2_d.describe.git_describe_workdir(&describe_result, repo, &opts.describe_options), "Failed to describe workdir", null);
95 		}
96 
97 		libgit2_d.buffer.git_buf buf = libgit2_d.buffer.git_buf.init;
98 		libgit2_d.example.common.check_lg2(libgit2_d.describe.git_describe_format(&buf, describe_result, &opts.format_options), "Failed to format describe rev", rev);
99 
100 		core.stdc.stdio.printf("%s\n", buf.ptr_);
101 	}
102 
103 nothrow @nogc
104 private void do_describe(libgit2_d.types.git_repository* repo, .describe_options* opts)
105 
106 	in
107 	{
108 	}
109 
110 	do
111 	{
112 		if (opts.commit_count == 0) {
113 			.do_describe_single(repo, opts, null);
114 		} else {
115 			for (size_t i = 0; i < opts.commit_count; i++) {
116 				.do_describe_single(repo, opts, opts.commits[i]);
117 			}
118 		}
119 	}
120 
121 nothrow @nogc
122 private void print_usage()
123 
124 	in
125 	{
126 	}
127 
128 	do
129 	{
130 		core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "usage: see `git help describe`\n");
131 		core.stdc.stdlib.exit(1);
132 	}
133 
134 /**
135  * Parse command line arguments
136  */
137 nothrow @nogc
138 private void parse_options(.describe_options* opts, int argc, char** argv)
139 
140 	in
141 	{
142 	}
143 
144 	do
145 	{
146 		libgit2_d.example.args.args_info args = libgit2_d.example.args.ARGS_INFO_INIT(argc, argv);
147 
148 		for (args.pos = 1; args.pos < argc; ++args.pos) {
149 			const (char)* curr = argv[args.pos];
150 
151 			if (curr[0] != '-') {
152 				.opts_add_commit(opts, curr);
153 			} else if (!core.stdc..string.strcmp(curr, "--all")) {
154 				opts.describe_options.describe_strategy = libgit2_d.describe.git_describe_strategy_t.GIT_DESCRIBE_ALL;
155 			} else if (!core.stdc..string.strcmp(curr, "--tags")) {
156 				opts.describe_options.describe_strategy = libgit2_d.describe.git_describe_strategy_t.GIT_DESCRIBE_TAGS;
157 			} else if (!core.stdc..string.strcmp(curr, "--exact-match")) {
158 				opts.describe_options.max_candidates_tags = 0;
159 			} else if (!core.stdc..string.strcmp(curr, "--long")) {
160 				opts.format_options.always_use_long_format = 1;
161 			} else if (!core.stdc..string.strcmp(curr, "--always")) {
162 				opts.describe_options.show_commit_oid_as_fallback = 1;
163 			} else if (!core.stdc..string.strcmp(curr, "--first-parent")) {
164 				opts.describe_options.only_follow_first_parent = 1;
165 			} else if (libgit2_d.example.args.optional_str_arg(&opts.format_options.dirty_suffix, &args, "--dirty", "-dirty")) {
166 			} else if (libgit2_d.example.args.match_int_arg(cast(int*)(&opts.format_options.abbreviated_size), &args, "--abbrev", 0)) {
167 			} else if (libgit2_d.example.args.match_int_arg(cast(int*)(&opts.describe_options.max_candidates_tags), &args, "--candidates", 0)) {
168 			} else if (libgit2_d.example.args.match_str_arg(&opts.describe_options.pattern, &args, "--match")) {
169 			} else {
170 				.print_usage();
171 			}
172 		}
173 
174 		if (opts.commit_count > 0) {
175 			if (opts.format_options.dirty_suffix) {
176 				libgit2_d.example.common.fatal("--dirty is incompatible with commit-ishes", null);
177 			}
178 		} else {
179 			if ((!opts.format_options.dirty_suffix) || (!opts.format_options.dirty_suffix[0])) {
180 				.opts_add_commit(opts, "HEAD");
181 			}
182 		}
183 	}
184 
185 /**
186  * Initialize describe_options struct
187  */
188 nothrow @nogc
189 private void describe_options_init(.describe_options* opts)
190 
191 	in
192 	{
193 	}
194 
195 	do
196 	{
197 		core.stdc..string.memset(opts, 0, (*opts).sizeof);
198 
199 		opts.commits = null;
200 		opts.commit_count = 0;
201 		libgit2_d.describe.git_describe_options_init(&opts.describe_options, libgit2_d.describe.GIT_DESCRIBE_OPTIONS_VERSION);
202 		libgit2_d.describe.git_describe_format_options_init(&opts.format_options, libgit2_d.describe.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION);
203 	}
204 
205 extern (C)
206 nothrow @nogc
207 public int lg2_describe(libgit2_d.types.git_repository* repo, int argc, char** argv)
208 
209 	in
210 	{
211 	}
212 
213 	do
214 	{
215 		.describe_options opts;
216 
217 		.describe_options_init(&opts);
218 		.parse_options(&opts, argc, argv);
219 
220 		.do_describe(repo, &opts);
221 
222 		return 0;
223 	}