1 /*
2  * Utilities library for libgit2 examples
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.common;
15 
16 
17 private static import core.stdc.errno;
18 private static import core.stdc.stdio;
19 private static import core.stdc.stdlib;
20 private static import core.stdc.string;
21 private static import core.sys.posix.fcntl;
22 private static import core.sys.posix.stdio;
23 private static import core.sys.posix.strings;
24 private static import core.sys.posix.sys.stat;
25 private static import core.sys.posix.sys.types;
26 private static import core.sys.posix.unistd;
27 private static import core.sys.windows.stat;
28 private static import core.sys.windows.winbase;
29 private static import libgit2_d.annotated_commit;
30 private static import libgit2_d.credential;
31 private static import libgit2_d.diff;
32 private static import libgit2_d.errors;
33 private static import libgit2_d.object;
34 private static import libgit2_d.refs;
35 private static import libgit2_d.revparse;
36 private static import libgit2_d.types;
37 
38 package:
39 
40 version (Windows) {
41 	alias open = core.stdc.stdio._open;
42 
43 	extern
44 	extern (C)
45 	nothrow @nogc @system
46 	int _read(int, void*, uint);
47 
48 	alias read = _read;
49 	alias close = core.stdc.stdio._close;
50 	alias ssize_t = int;
51 
52 	pragma(inline, true)
53 	nothrow @nogc
54 	void sleep(int a)
55 
56 		do
57 		{
58 			core.sys.windows.winbase.Sleep(a * 1000);
59 		}
60 
61 	alias O_RDONLY = core.stdc.stdio.O_RDONLY;
62 	alias stat = core.sys.windows.stat.struct_stat;
63 	alias fstat = core.sys.windows.stat.fstat;
64 } else {
65 	//package static import core.sys.posix.unistd;
66 
67 	alias open = core.sys.posix.fcntl.open;
68 	alias read = core.sys.posix.unistd.read;
69 	alias close = core.sys.posix.unistd.close;
70 	alias ssize_t = core.sys.posix.sys.types.ssize_t;
71 	alias sleep = core.sys.posix.unistd.sleep;
72 	alias O_RDONLY = core.sys.posix.fcntl.O_RDONLY;
73 	alias stat = core.sys.posix.sys.stat.stat_t;
74 	alias fstat = core.sys.posix.sys.stat.fstat;
75 }
76 
77 /* Define the printf format specifer to use for size_t output */
78 //#if defined(_MSC_VER) || defined(__MINGW32__)
79 version (Windows) {
80 	enum PRIuZ = "Iu";
81 } else {
82 	enum PRIuZ = "zu";
83 }
84 
85 version (Windows) {
86 	alias snprintf = core.stdc.stdio.snprintf;
87 	//alias strcasecmp = strcmpi;
88 } else {
89 	alias snprintf = core.sys.posix.stdio.snprintf;
90 	alias strcasecmp = core.sys.posix.strings.strcasecmp;
91 }
92 
93 /**
94  * Check libgit2 error code, printing error to stderr on failure and
95  * exiting the program.
96  */
97 nothrow @nogc
98 public void check_lg2(int error, const (char)* message, const (char)* extra)
99 
100 	in
101 	{
102 	}
103 
104 	do
105 	{
106 		if (!error) {
107 			return;
108 		}
109 
110 		const (libgit2_d.errors.git_error)* lg2err = libgit2_d.errors.git_error_last();
111 		const (char)* lg2msg = "";
112 		const (char)* lg2spacer = "";
113 
114 		if ((lg2err != null) && (lg2err.message != null)) {
115 			lg2msg = lg2err.message;
116 			lg2spacer = " - ";
117 		}
118 
119 		if (extra != null) {
120 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s '%s' [%d]%s%s\n", message, extra, error, lg2spacer, lg2msg);
121 		} else {
122 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s [%d]%s%s\n", message, error, lg2spacer, lg2msg);
123 		}
124 
125 		core.stdc.stdlib.exit(1);
126 	}
127 
128 /**
129  * Exit the program, printing error to stderr
130  */
131 nothrow @nogc
132 public void fatal(const (char)* message, const (char)* extra)
133 
134 	in
135 	{
136 	}
137 
138 	do
139 	{
140 		if (extra != null) {
141 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s %s\n", message, extra);
142 		} else {
143 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s\n", message);
144 		}
145 
146 		core.stdc.stdlib.exit(1);
147 	}
148 
149 /**
150  * Basic output function for plain text diff output
151  * Pass `core.stdc.stdio.FILE*` such as `core.stdc.stdio.stdout` or `core.stdc.stdio.stderr` as payload (or null == `core.stdc.stdio.stdout`)
152  */
153 extern (C)
154 nothrow @nogc
155 public int diff_output(const (libgit2_d.diff.git_diff_delta)* d, const (libgit2_d.diff.git_diff_hunk)* h, const (libgit2_d.diff.git_diff_line)* l, void* p)
156 
157 	in
158 	{
159 	}
160 
161 	do
162 	{
163 		core.stdc.stdio.FILE* fp = cast(core.stdc.stdio.FILE*)(p);
164 
165 		//cast(void)(d);
166 		//cast(void)(h);
167 
168 		if (fp == null) {
169 			fp = core.stdc.stdio.stdout;
170 		}
171 
172 		if ((l.origin == libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_CONTEXT) || (l.origin == libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_ADDITION) || (l.origin == libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_DELETION)) {
173 			core.stdc.stdio.fputc(l.origin, fp);
174 		}
175 
176 		core.stdc.stdio.fwrite(l.content, 1, l.content_len, fp);
177 
178 		return 0;
179 	}
180 
181 /**
182  * Convert a treeish argument to an actual tree; this will call check_lg2
183  * and exit the program if `treeish` cannot be resolved to a tree
184  */
185 nothrow @nogc
186 public void treeish_to_tree(libgit2_d.types.git_tree** out_, libgit2_d.types.git_repository* repo, const (char)* treeish)
187 
188 	in
189 	{
190 	}
191 
192 	do
193 	{
194 		libgit2_d.types.git_object* obj = null;
195 
196 		.check_lg2(libgit2_d.revparse.git_revparse_single(&obj, repo, treeish), "looking up object", treeish);
197 
198 		.check_lg2(libgit2_d.object.git_object_peel(cast(libgit2_d.types.git_object**)(out_), obj, libgit2_d.types.git_object_t.GIT_OBJECT_TREE), "resolving object to tree", treeish);
199 
200 		libgit2_d.object.git_object_free(obj);
201 	}
202 
203 /**
204  * A realloc that exits on failure
205  */
206 nothrow @nogc
207 public void* xrealloc(void* oldp, size_t newsz)
208 
209 	in
210 	{
211 	}
212 
213 	do
214 	{
215 		void* p = core.stdc.stdlib.realloc(oldp, newsz);
216 
217 		if (p == null) {
218 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "Cannot allocate memory, exiting.\n");
219 			core.stdc.stdlib.exit(1);
220 		}
221 
222 		return p;
223 	}
224 
225 /**
226  * Convert a refish to an annotated commit.
227  */
228 nothrow @nogc
229 public int resolve_refish(libgit2_d.types.git_annotated_commit** commit, libgit2_d.types.git_repository* repo, const (char)* refish)
230 
231 	in
232 	{
233 		assert(commit != null);
234 	}
235 
236 	do
237 	{
238 		libgit2_d.types.git_reference* ref_;
239 		int err = libgit2_d.refs.git_reference_dwim(&ref_, repo, refish);
240 
241 		if (err == libgit2_d.errors.git_error_code.GIT_OK) {
242 			libgit2_d.annotated_commit.git_annotated_commit_from_ref(commit, repo, ref_);
243 			libgit2_d.refs.git_reference_free(ref_);
244 
245 			return 0;
246 		}
247 
248 		libgit2_d.types.git_object* obj;
249 		err = libgit2_d.revparse.git_revparse_single(&obj, repo, refish);
250 
251 		if (err == libgit2_d.errors.git_error_code.GIT_OK) {
252 			err = libgit2_d.annotated_commit.git_annotated_commit_lookup(commit, repo, libgit2_d.object.git_object_id(obj));
253 			libgit2_d.object.git_object_free(obj);
254 		}
255 
256 		return err;
257 	}
258 
259 nothrow @nogc
260 private int readline(char** out_)
261 
262 	in
263 	{
264 	}
265 
266 	do
267 	{
268 		int c;
269 		int error = 0;
270 		int length = 0;
271 		int allocated = 0;
272 		char* line = null;
273 
274 		scope (exit) {
275 			if (line != null) {
276 				core.stdc.stdlib.free(line);
277 				line = null;
278 			}
279 		}
280 
281 		core.stdc.errno.errno = 0;
282 
283 		while ((c = core.stdc.stdio.getchar()) != core.stdc.stdio.EOF) {
284 			if (length == allocated) {
285 				allocated += 16;
286 				line = cast(char*)(core.stdc.stdlib.realloc(line, allocated));
287 
288 				if (line == null) {
289 					error = -1;
290 
291 					return error;
292 				}
293 			}
294 
295 			if (c == '\n') {
296 				break;
297 			}
298 
299 			line[length++] = cast(char)(c);
300 		}
301 
302 		if (core.stdc.errno.errno != 0) {
303 			error = -1;
304 
305 			return error;
306 		}
307 
308 		line[length] = '\0';
309 		*out_ = line;
310 		line = null;
311 		error = length;
312 
313 		return error;
314 	}
315 
316 nothrow @nogc
317 private int ask(char** out_, const (char)* prompt, char optional)
318 
319 	in
320 	{
321 	}
322 
323 	do
324 	{
325 		core.stdc.stdio.printf("%s ", prompt);
326 		core.stdc.stdio.fflush(core.stdc.stdio.stdout);
327 
328 		if ((!.readline(out_)) && (!optional)) {
329 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "Could not read response: %s", core.stdc..string.strerror(core.stdc.errno.errno));
330 
331 			return -1;
332 		}
333 
334 		return 0;
335 	}
336 
337 /**
338  * Acquire credentials via command line
339  */
340 extern (C)
341 nothrow @nogc
342 public int cred_acquire_cb(libgit2_d.credential.git_credential** out_, const (char)* url, const (char)* username_from_url, uint allowed_types, void* payload)
343 
344 	in
345 	{
346 	}
347 
348 	do
349 	{
350 		char* username = null;
351 		char* password = null;
352 		char* privkey = null;
353 		char* pubkey = null;
354 		int error = 1;
355 
356 		//cast(void)(url);
357 		//cast(void)(payload);
358 
359 		scope (exit) {
360 			if (username != null) {
361 				core.stdc.stdlib.free(username);
362 				username = null;
363 			}
364 
365 			if (password != null) {
366 				core.stdc.stdlib.free(password);
367 				password = null;
368 			}
369 
370 			if (privkey != null) {
371 				core.stdc.stdlib.free(privkey);
372 				privkey = null;
373 			}
374 
375 			if (pubkey != null) {
376 				core.stdc.stdlib.free(pubkey);
377 				pubkey = null;
378 			}
379 		}
380 
381 		if (username_from_url != null) {
382 			username = core.stdc..string.strdup(username_from_url);
383 
384 			if (username == null) {
385 				return error;
386 			}
387 		} else {
388 			error = .ask(&username, "Username:", 0);
389 
390 			if (error < 0) {
391 				return error;
392 			}
393 		}
394 
395 		if (allowed_types & libgit2_d.credential.git_credential_t.GIT_CREDENTIAL_SSH_KEY) {
396 			int n;
397 
398 			error = .ask(&privkey, "SSH Key:", 0);
399 
400 			if (error < 0) {
401 				return error;
402 			}
403 
404 			error = .ask(&password, "Password:", 1);
405 
406 			if (error < 0) {
407 				return error;
408 			}
409 
410 			n = .snprintf(null, 0, "%s.pub", privkey);
411 
412 			if (n < 0) {
413 				return error;
414 			}
415 
416 			pubkey = cast(char*)(core.stdc.stdlib.malloc(n + 1));
417 
418 			if (pubkey == null) {
419 				return error;
420 			}
421 
422 			n = .snprintf(pubkey, n + 1, "%s.pub", privkey);
423 
424 			if (n < 0) {
425 				return error;
426 			}
427 
428 			error = libgit2_d.credential.git_credential_ssh_key_new(out_, username, pubkey, privkey, password);
429 		} else if (allowed_types & libgit2_d.credential.git_credential_t.GIT_CREDENTIAL_USERPASS_PLAINTEXT) {
430 			error = .ask(&password, "Password:", 1);
431 
432 			if (error < 0) {
433 				return error;
434 			}
435 
436 			error = libgit2_d.credential.git_credential_userpass_plaintext_new(out_, username, password);
437 		} else if (allowed_types & libgit2_d.credential.git_credential_t.GIT_CREDENTIAL_USERNAME) {
438 			error = libgit2_d.credential.git_credential_username_new(out_, username);
439 		}
440 
441 		return error;
442 	}
443 
444 /**
445  * Read a file into a buffer
446  *
447  * @param path The path to the file that shall be read
448  * @return NUL-terminated buffer if the file was successfully read, null-pointer otherwise
449  */
450 nothrow @nogc
451 public char* read_file(const (char)* path)
452 
453 	in
454 	{
455 	}
456 
457 	do
458 	{
459 		int fd = .open(path, .O_RDONLY);
460 
461 		if (fd < 0) {
462 			return null;
463 		}
464 
465 		scope (exit) {
466 			if (fd >= 0) {
467 				.close(fd);
468 			}
469 		}
470 
471 		.stat st;
472 
473 		if (.fstat(fd, &st) < 0) {
474 			return null;
475 		}
476 
477 		char* buf = cast(char*)(core.stdc.stdlib.malloc(st.st_size + 1));
478 
479 		if (buf == null) {
480 			return buf;
481 		}
482 
483 		.ssize_t total = 0;
484 
485 		while (total < st.st_size) {
486 			.ssize_t bytes = .read(fd, buf + total, st.st_size - total);
487 
488 			if (bytes <= 0) {
489 				if ((core.stdc.errno.errno == core.stdc.errno.EAGAIN) || (core.stdc.errno.errno == core.stdc.errno.EINTR)) {
490 					continue;
491 				}
492 
493 				core.stdc.stdlib.free(buf);
494 				buf = null;
495 
496 				return buf;
497 			}
498 
499 			total += bytes;
500 		}
501 
502 		buf[total] = '\0';
503 
504 		return buf;
505 	}