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 }