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 }