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