1 /* 2 * libgit2 "status" example - shows how to use the status APIs 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.status; 15 16 17 private static import core.stdc.stdio; 18 private static import core.stdc..string; 19 private static import libgit2_d.diff; 20 private static import libgit2_d.errors; 21 private static import libgit2_d.example.args; 22 private static import libgit2_d.example.common; 23 private static import libgit2_d.refs; 24 private static import libgit2_d.repository; 25 private static import libgit2_d.status; 26 private static import libgit2_d.submodule; 27 private static import libgit2_d.types; 28 29 package: 30 31 /** 32 * This example demonstrates the use of the libgit2 status APIs, 33 * particularly the `libgit2_d.types.git_status_list` object, to roughly simulate the 34 * output of running `git status`. It serves as a simple example of 35 * using those APIs to get basic status information. 36 * 37 * This does not have: 38 * 39 * - Robust error handling 40 * - Colorized or paginated output formatting 41 * 42 * This does have: 43 * 44 * - Examples of translating command line arguments to the status 45 * options settings to mimic `git status` results. 46 * - A sample status formatter that matches the default "long" format 47 * from `git status` 48 * - A sample status formatter that matches the "short" format 49 */ 50 51 public enum 52 { 53 FORMAT_DEFAULT = 0, 54 FORMAT_LONG = 1, 55 FORMAT_SHORT = 2, 56 FORMAT_PORCELAIN = 3, 57 } 58 59 public enum MAX_PATHSPEC = 8; 60 61 public struct status_opts 62 { 63 libgit2_d.status.git_status_options statusopt; 64 const (char)* repodir; 65 char*[.MAX_PATHSPEC] pathspec; 66 int npaths; 67 int format; 68 int zterm; 69 int showbranch; 70 int showsubmod; 71 int repeat; 72 } 73 74 extern (C) 75 nothrow @nogc 76 //int lg2_status(libgit2_d.types.git_repository* repo, int argc, char*[] argv) 77 public int lg2_status(libgit2_d.types.git_repository* repo, int argc, char** argv) 78 79 in 80 { 81 } 82 83 do 84 { 85 libgit2_d.types.git_status_list* status; 86 .status_opts o = {libgit2_d.status.GIT_STATUS_OPTIONS_INIT(), ".".ptr}; 87 88 o.statusopt.show = libgit2_d.status.git_status_show_t.GIT_STATUS_SHOW_INDEX_AND_WORKDIR; 89 o.statusopt.flags = libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_INCLUDE_UNTRACKED | libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; 90 91 .parse_opts(&o, argc, argv); 92 93 if (libgit2_d.repository.git_repository_is_bare(repo)) { 94 libgit2_d.example.common.fatal("Cannot report status on bare repository", libgit2_d.repository.git_repository_path(repo)); 95 } 96 97 show_status: 98 if (o.repeat) { 99 core.stdc.stdio.printf("\033[H\033[2J"); 100 } 101 102 /** 103 * Run status on the repository 104 * 105 * We use `libgit2_d.status.git_status_list_new()` to generate a list of status 106 * information which lets us iterate over it at our 107 * convenience and extract the data we want to show out of 108 * each entry. 109 * 110 * You can use `libgit2_d.status.git_status_foreach()` or 111 * `libgit2_d.status.git_status_foreach_ext()` if you'd prefer to execute a 112 * callback for each entry. The latter gives you more control 113 * about what results are presented. 114 */ 115 libgit2_d.example.common.check_lg2(libgit2_d.status.git_status_list_new(&status, repo, &o.statusopt), "Could not get status", null); 116 117 if (o.showbranch) { 118 .show_branch(repo, o.format); 119 } 120 121 if (o.showsubmod) { 122 int submod_count = 0; 123 libgit2_d.example.common.check_lg2(libgit2_d.submodule.git_submodule_foreach(repo, &.print_submod, &submod_count), "Cannot iterate submodules", o.repodir); 124 } 125 126 if (o.format == .FORMAT_LONG) { 127 .print_long(status); 128 } else { 129 .print_short(repo, status); 130 } 131 132 libgit2_d.status.git_status_list_free(status); 133 134 if (o.repeat) { 135 libgit2_d.example.common.sleep(o.repeat); 136 137 goto show_status; 138 } 139 140 return 0; 141 } 142 143 /** 144 * If the user asked for the branch, let's show the short name of the 145 * branch. 146 */ 147 nothrow @nogc 148 private void show_branch(libgit2_d.types.git_repository* repo, int format) 149 150 in 151 { 152 } 153 154 do 155 { 156 const (char)* branch = null; 157 libgit2_d.types.git_reference* head = null; 158 159 int error = libgit2_d.repository.git_repository_head(&head, repo); 160 161 if ((error == libgit2_d.errors.git_error_code.GIT_EUNBORNBRANCH) || (error == libgit2_d.errors.git_error_code.GIT_ENOTFOUND)) { 162 branch = null; 163 } else if (!error) { 164 branch = libgit2_d.refs.git_reference_shorthand(head); 165 } else { 166 libgit2_d.example.common.check_lg2(error, "failed to get current branch", null); 167 } 168 169 if (format == .FORMAT_LONG) { 170 core.stdc.stdio.printf("# On branch %s\n", (branch) ? (branch) : ("Not currently on any branch.")); 171 } else { 172 core.stdc.stdio.printf("## %s\n", (branch) ? (branch) : ("HEAD (no branch)")); 173 } 174 175 libgit2_d.refs.git_reference_free(head); 176 } 177 178 /** 179 * This function print out an output similar to git's status command 180 * in long form, including the command-line hints. 181 */ 182 nothrow @nogc 183 private void print_long(libgit2_d.types.git_status_list* status) 184 185 in 186 { 187 } 188 189 do 190 { 191 size_t maxi = libgit2_d.status.git_status_list_entrycount(status); 192 const (libgit2_d.status.git_status_entry)* s; 193 int header = 0; 194 int rm_in_workdir = 0; 195 196 /** Print index changes. */ 197 198 for (size_t i = 0; i < maxi; ++i) { 199 const (char)* istatus = null; 200 201 s = libgit2_d.status.git_status_byindex(status, i); 202 203 if (s.status == libgit2_d.status.git_status_t.GIT_STATUS_CURRENT) { 204 continue; 205 } 206 207 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_DELETED) { 208 rm_in_workdir = 1; 209 } 210 211 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_NEW) { 212 istatus = "new file: "; 213 } 214 215 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_MODIFIED) { 216 istatus = "modified: "; 217 } 218 219 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_DELETED) { 220 istatus = "deleted: "; 221 } 222 223 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_RENAMED) { 224 istatus = "renamed: "; 225 } 226 227 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_TYPECHANGE) { 228 istatus = "typechange:"; 229 } 230 231 if (istatus == null) { 232 continue; 233 } 234 235 if (!header) { 236 core.stdc.stdio.printf("# Changes to be committed:\n"); 237 core.stdc.stdio.printf("# (use \"git reset HEAD <file>...\" to unstage)\n"); 238 core.stdc.stdio.printf("#\n"); 239 header = 1; 240 } 241 242 const (char)* old_path = s.head_to_index.old_file.path; 243 const (char)* new_path = s.head_to_index.new_file.path; 244 245 if ((old_path) && (new_path) && (core.stdc..string.strcmp(old_path, new_path))) { 246 core.stdc.stdio.printf("#\t%s %s . %s\n", istatus, old_path, new_path); 247 } else { 248 core.stdc.stdio.printf("#\t%s %s\n", istatus, (old_path) ? (old_path) : (new_path)); 249 } 250 } 251 252 int changes_in_index = 0; 253 254 if (header) { 255 changes_in_index = 1; 256 core.stdc.stdio.printf("#\n"); 257 } 258 259 header = 0; 260 261 /** Print workdir changes to tracked files. */ 262 263 for (size_t i = 0; i < maxi; ++i) { 264 const (char)* wstatus = null; 265 266 s = libgit2_d.status.git_status_byindex(status, i); 267 268 /** 269 * With `libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example) 270 * `index_to_workdir` may not be `null` even if there are 271 * no differences, in which case it will be a `libgit2_d.diff.git_delta_t.GIT_DELTA_UNMODIFIED`. 272 */ 273 if ((s.status == libgit2_d.status.git_status_t.GIT_STATUS_CURRENT) || (s.index_to_workdir == null)) { 274 continue; 275 } 276 277 /** Print out the output since we know the file has some changes */ 278 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_MODIFIED) { 279 wstatus = "modified: "; 280 } 281 282 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_DELETED) { 283 wstatus = "deleted: "; 284 } 285 286 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_RENAMED) { 287 wstatus = "renamed: "; 288 } 289 290 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_TYPECHANGE) { 291 wstatus = "typechange:"; 292 } 293 294 if (wstatus == null) { 295 continue; 296 } 297 298 if (!header) { 299 core.stdc.stdio.printf("# Changes not staged for commit:\n"); 300 core.stdc.stdio.printf("# (use \"git add%s <file>...\" to update what will be committed)\n", (rm_in_workdir) ? (&("/rm\0"[0])) : (&("\0"[0]))); 301 core.stdc.stdio.printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n"); 302 core.stdc.stdio.printf("#\n"); 303 header = 1; 304 } 305 306 const (char)* old_path = s.index_to_workdir.old_file.path; 307 const (char)* new_path = s.index_to_workdir.new_file.path; 308 309 if ((old_path) && (new_path) && (core.stdc..string.strcmp(old_path, new_path))) { 310 core.stdc.stdio.printf("#\t%s %s . %s\n", wstatus, old_path, new_path); 311 } else { 312 core.stdc.stdio.printf("#\t%s %s\n", wstatus, (old_path) ? (old_path) : (new_path)); 313 } 314 } 315 316 int changed_in_workdir = 0; 317 318 if (header) { 319 changed_in_workdir = 1; 320 core.stdc.stdio.printf("#\n"); 321 } 322 323 /** Print untracked files. */ 324 325 header = 0; 326 327 for (size_t i = 0; i < maxi; ++i) { 328 s = libgit2_d.status.git_status_byindex(status, i); 329 330 if (s.status == libgit2_d.status.git_status_t.GIT_STATUS_WT_NEW) { 331 if (!header) { 332 core.stdc.stdio.printf("# Untracked files:\n"); 333 core.stdc.stdio.printf("# (use \"git add <file>...\" to include in what will be committed)\n"); 334 core.stdc.stdio.printf("#\n"); 335 header = 1; 336 } 337 338 core.stdc.stdio.printf("#\t%s\n", s.index_to_workdir.old_file.path); 339 } 340 } 341 342 header = 0; 343 344 /** Print ignored files. */ 345 346 for (size_t i = 0; i < maxi; ++i) { 347 s = libgit2_d.status.git_status_byindex(status, i); 348 349 if (s.status == libgit2_d.status.git_status_t.GIT_STATUS_IGNORED) { 350 if (!header) { 351 core.stdc.stdio.printf("# Ignored files:\n"); 352 core.stdc.stdio.printf("# (use \"git add -f <file>...\" to include in what will be committed)\n"); 353 core.stdc.stdio.printf("#\n"); 354 header = 1; 355 } 356 357 core.stdc.stdio.printf("#\t%s\n", s.index_to_workdir.old_file.path); 358 } 359 } 360 361 if ((!changes_in_index) && (changed_in_workdir)) { 362 core.stdc.stdio.printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); 363 } 364 } 365 366 /** 367 * This version of the output prefixes each path with two status 368 * columns and shows submodule status information. 369 */ 370 nothrow @nogc 371 private void print_short(libgit2_d.types.git_repository* repo, libgit2_d.types.git_status_list* status) 372 373 in 374 { 375 } 376 377 do 378 { 379 size_t maxi = libgit2_d.status.git_status_list_entrycount(status); 380 381 for (size_t i = 0; i < maxi; ++i) { 382 const (libgit2_d.status.git_status_entry)* s = libgit2_d.status.git_status_byindex(status, i); 383 384 if (s.status == libgit2_d.status.git_status_t.GIT_STATUS_CURRENT) { 385 continue; 386 } 387 388 const (char)* c = null; 389 const (char)* b = null; 390 const (char)* a = null; 391 char wstatus = ' '; 392 char istatus = ' '; 393 const (char)* extra = ""; 394 395 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_NEW) { 396 istatus = 'A'; 397 } 398 399 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_MODIFIED) { 400 istatus = 'M'; 401 } 402 403 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_DELETED) { 404 istatus = 'D'; 405 } 406 407 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_RENAMED) { 408 istatus = 'R'; 409 } 410 411 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_INDEX_TYPECHANGE) { 412 istatus = 'T'; 413 } 414 415 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_NEW) { 416 if (istatus == ' ') { 417 istatus = '?'; 418 } 419 420 wstatus = '?'; 421 } 422 423 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_MODIFIED) { 424 wstatus = 'M'; 425 } 426 427 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_DELETED) { 428 wstatus = 'D'; 429 } 430 431 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_RENAMED) { 432 wstatus = 'R'; 433 } 434 435 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_WT_TYPECHANGE) { 436 wstatus = 'T'; 437 } 438 439 if (s.status & libgit2_d.status.git_status_t.GIT_STATUS_IGNORED) { 440 istatus = '!'; 441 wstatus = '!'; 442 } 443 444 if (istatus == '?' && wstatus == '?') { 445 continue; 446 } 447 448 /** 449 * A commit in a tree is how submodules are stored, so 450 * let's go take a look at its status. 451 */ 452 if ((s.index_to_workdir) && (s.index_to_workdir.new_file.mode == libgit2_d.types.git_filemode_t.GIT_FILEMODE_COMMIT)) { 453 uint smstatus = 0; 454 455 if (!libgit2_d.submodule.git_submodule_status(&smstatus, repo, s.index_to_workdir.new_file.path, libgit2_d.types.git_submodule_ignore_t.GIT_SUBMODULE_IGNORE_UNSPECIFIED)) { 456 if (smstatus & libgit2_d.submodule.git_submodule_status_t.GIT_SUBMODULE_STATUS_WD_MODIFIED) { 457 extra = " (new commits)"; 458 } else if (smstatus & libgit2_d.submodule.git_submodule_status_t.GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) { 459 extra = " (modified content)"; 460 } else if (smstatus & libgit2_d.submodule.git_submodule_status_t.GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) { 461 extra = " (modified content)"; 462 } else if (smstatus & libgit2_d.submodule.git_submodule_status_t.GIT_SUBMODULE_STATUS_WD_UNTRACKED) { 463 extra = " (untracked content)"; 464 } 465 } 466 } 467 468 /** 469 * Now that we have all the information, format the output. 470 */ 471 472 if (s.head_to_index) { 473 a = s.head_to_index.old_file.path; 474 b = s.head_to_index.new_file.path; 475 } 476 477 if (s.index_to_workdir) { 478 if (a == null) { 479 a = s.index_to_workdir.old_file.path; 480 } 481 482 if (b == null) { 483 b = s.index_to_workdir.old_file.path; 484 } 485 486 c = s.index_to_workdir.new_file.path; 487 } 488 489 if (istatus == 'R') { 490 if (wstatus == 'R') { 491 core.stdc.stdio.printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra); 492 } else { 493 core.stdc.stdio.printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra); 494 } 495 } else { 496 if (wstatus == 'R') { 497 core.stdc.stdio.printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra); 498 } else { 499 core.stdc.stdio.printf("%c%c %s%s\n", istatus, wstatus, a, extra); 500 } 501 } 502 } 503 504 for (size_t i = 0; i < maxi; ++i) { 505 const (libgit2_d.status.git_status_entry)* s = libgit2_d.status.git_status_byindex(status, i); 506 507 if (s.status == libgit2_d.status.git_status_t.GIT_STATUS_WT_NEW) { 508 core.stdc.stdio.printf("?? %s\n", s.index_to_workdir.old_file.path); 509 } 510 } 511 } 512 513 extern (C) 514 nothrow @nogc 515 private int print_submod(libgit2_d.types.git_submodule* sm, const (char)* name, void* payload) 516 517 in 518 { 519 } 520 521 do 522 { 523 //cast(void)(name); 524 int* count = cast(int*)(payload); 525 526 if (*count == 0) { 527 core.stdc.stdio.printf("# Submodules\n"); 528 } 529 530 (*count)++; 531 532 core.stdc.stdio.printf("# - submodule '%s' at %s\n", libgit2_d.submodule.git_submodule_name(sm), libgit2_d.submodule.git_submodule_path(sm)); 533 534 return 0; 535 } 536 537 /** 538 * Parse options that git's status command supports. 539 */ 540 nothrow @nogc 541 private void parse_opts(.status_opts* o, int argc, char** argv) 542 543 in 544 { 545 } 546 547 do 548 { 549 libgit2_d.example.args.args_info args = libgit2_d.example.args.ARGS_INFO_INIT(argc, argv); 550 551 for (args.pos = 1; args.pos < argc; ++args.pos) { 552 char* a = argv[args.pos]; 553 554 if (a[0] != '-') { 555 if (o.npaths < .MAX_PATHSPEC) { 556 o.pathspec[o.npaths++] = a; 557 } else { 558 libgit2_d.example.common.fatal("Example only supports a limited pathspec", null); 559 } 560 } else if ((!core.stdc..string.strcmp(a, "-s")) || (!core.stdc..string.strcmp(a, "--short"))) { 561 o.format = .FORMAT_SHORT; 562 } else if (!core.stdc..string.strcmp(a, "--long")) { 563 o.format = .FORMAT_LONG; 564 } else if (!core.stdc..string.strcmp(a, "--porcelain")) { 565 o.format = .FORMAT_PORCELAIN; 566 } else if ((!core.stdc..string.strcmp(a, "-b")) || (!core.stdc..string.strcmp(a, "--branch"))) { 567 o.showbranch = 1; 568 } else if (!core.stdc..string.strcmp(a, "-z")) { 569 o.zterm = 1; 570 571 if (o.format == .FORMAT_DEFAULT) { 572 o.format = .FORMAT_PORCELAIN; 573 } 574 } else if (!core.stdc..string.strcmp(a, "--ignored")) { 575 o.statusopt.flags |= libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_INCLUDE_IGNORED; 576 } else if ((!core.stdc..string.strcmp(a, "-uno")) || (!core.stdc..string.strcmp(a, "--untracked-files=no"))) { 577 o.statusopt.flags &= ~libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_INCLUDE_UNTRACKED; 578 } else if ((!core.stdc..string.strcmp(a, "-unormal")) || (!core.stdc..string.strcmp(a, "--untracked-files=normal"))) { 579 o.statusopt.flags |= libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_INCLUDE_UNTRACKED; 580 } else if ((!core.stdc..string.strcmp(a, "-uall")) || (!core.stdc..string.strcmp(a, "--untracked-files=all"))) { 581 o.statusopt.flags |= libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_INCLUDE_UNTRACKED | libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; 582 } else if (!core.stdc..string.strcmp(a, "--ignore-submodules=all")) { 583 o.statusopt.flags |= libgit2_d.status.git_status_opt_t.GIT_STATUS_OPT_EXCLUDE_SUBMODULES; 584 } else if (!core.stdc..string.strncmp(a, "--git-dir=", core.stdc..string.strlen("--git-dir="))) { 585 o.repodir = a + core.stdc..string.strlen("--git-dir="); 586 } else if (!core.stdc..string.strcmp(a, "--repeat")) { 587 o.repeat = 10; 588 } else if (libgit2_d.example.args.match_int_arg(&o.repeat, &args, "--repeat", 0)) { 589 /* okay */ 590 } else if (!core.stdc..string.strcmp(a, "--list-submodules")) { 591 o.showsubmod = 1; 592 } else { 593 libgit2_d.example.common.check_lg2(-1, "Unsupported option", a); 594 } 595 } 596 597 if (o.format == .FORMAT_DEFAULT) { 598 o.format = .FORMAT_LONG; 599 } 600 601 if (o.format == .FORMAT_LONG) { 602 o.showbranch = 1; 603 } 604 605 if (o.npaths > 0) { 606 o.statusopt.pathspec.strings = &(o.pathspec[0]); 607 o.statusopt.pathspec.count = o.npaths; 608 } 609 }