1 /* 2 * libgit2 "diff" example - shows how to use the diff API 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.diff; 15 16 17 private static import core.stdc.stdio; 18 private static import core.stdc.stdlib; 19 private static import core.stdc.string; 20 private static import libgit2_d.buffer; 21 private static import libgit2_d.diff; 22 private static import libgit2_d.example.args; 23 private static import libgit2_d.example.common; 24 private static import libgit2_d.patch; 25 private static import libgit2_d.tree; 26 private static import libgit2_d.types; 27 28 package: 29 30 /** 31 * This example demonstrates the use of the libgit2 diff APIs to 32 * create `libgit2_d.diff.git_diff` objects and display them, emulating a number of 33 * core Git `diff` command line options. 34 * 35 * This covers on a portion of the core Git diff options and doesn't 36 * have particularly good error handling, but it should show most of 37 * the core libgit2 diff APIs, including various types of diffs and 38 * how to do renaming detection and patch formatting. 39 */ 40 41 private const (char)*[] colors = 42 [ 43 "\033[m", /* reset */ 44 "\033[1m", /* bold */ 45 "\033[31m", /* red */ 46 "\033[32m", /* green */ 47 "\033[36m", /* cyan */ 48 ]; 49 50 public enum 51 { 52 OUTPUT_DIFF = 1 << 0, 53 OUTPUT_STAT = 1 << 1, 54 OUTPUT_SHORTSTAT = 1 << 2, 55 OUTPUT_NUMSTAT = 1 << 3, 56 OUTPUT_SUMMARY = 1 << 4, 57 } 58 59 public enum 60 { 61 CACHE_NORMAL = 0, 62 CACHE_ONLY = 1, 63 CACHE_NONE = 2, 64 } 65 66 /** 67 * The 'diff_options' struct captures all the various parsed command line options. 68 */ 69 public struct diff_options 70 { 71 libgit2_d.diff.git_diff_options diffopts; 72 libgit2_d.diff.git_diff_find_options findopts; 73 int color; 74 int no_index; 75 int cache; 76 int output; 77 libgit2_d.diff.git_diff_format_t format; 78 const (char)* treeish1; 79 const (char)* treeish2; 80 const (char)* dir; 81 } 82 83 /** These functions are implemented at the end */ 84 //private void usage(const (char)* message, const (char)* arg); 85 //private void parse_opts(.diff_options* o, int argc, char*[] argv); 86 //private int color_printer(const (libgit2_d.diff.git_diff_delta)*, const (libgit2_d.diff.git_diff_hunk)*, const (libgit2_d.diff.git_diff_line)*, void*); 87 //private void diff_print_stats(libgit2_d.diff.git_diff* diff, .diff_options* o); 88 //private void compute_diff_no_index(libgit2_d.diff.git_diff** diff, .diff_options* o); 89 90 extern (C) 91 nothrow @nogc 92 //int lg2_diff(libgit2_d.types.git_repository* repo, int argc, char*[] argv) 93 public int lg2_diff(libgit2_d.types.git_repository* repo, int argc, char** argv) 94 95 in 96 { 97 } 98 99 do 100 { 101 libgit2_d.types.git_tree* t1 = null; 102 libgit2_d.types.git_tree* t2 = null; 103 libgit2_d.diff.git_diff* diff; 104 105 .diff_options o = {libgit2_d.diff.GIT_DIFF_OPTIONS_INIT(), libgit2_d.diff.GIT_DIFF_FIND_OPTIONS_INIT(), -1, -1, 0, 0, libgit2_d.diff.git_diff_format_t.GIT_DIFF_FORMAT_PATCH, null, null, "."}; 106 107 .parse_opts(&o, argc, argv); 108 109 /** 110 * Possible argument patterns: 111 * 112 * * <sha1> <sha2> 113 * * <sha1> --cached 114 * * <sha1> 115 * * --cached 116 * * --nocache (don't use index data in diff at all) 117 * * --no-index <file1> <file2> 118 * * nothing 119 * 120 * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> 121 * are not supported in this example 122 */ 123 124 if (o.no_index >= 0) { 125 .compute_diff_no_index(&diff, &o); 126 } else { 127 if (o.treeish1 != null) { 128 libgit2_d.example.common.treeish_to_tree(&t1, repo, o.treeish1); 129 } 130 131 if (o.treeish2 != null) { 132 libgit2_d.example.common.treeish_to_tree(&t2, repo, o.treeish2); 133 } 134 135 if ((t1 != null) && (t2 != null)) { 136 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts), "diff trees", null); 137 } else if (o.cache != .CACHE_NORMAL) { 138 if (t1 == null) { 139 libgit2_d.example.common.treeish_to_tree(&t1, repo, "HEAD"); 140 } 141 142 if (o.cache == .CACHE_NONE) { 143 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts), "diff tree to working directory", null); 144 } else { 145 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_tree_to_index(&diff, repo, t1, null, &o.diffopts), "diff tree to index", null); 146 } 147 } else if (t1 != null) { 148 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts), "diff tree to working directory", null); 149 } else { 150 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_index_to_workdir(&diff, repo, null, &o.diffopts), "diff index to working directory", null); 151 } 152 153 /** Apply rename and copy detection if requested. */ 154 155 if ((o.findopts.flags & libgit2_d.diff.git_diff_find_t.GIT_DIFF_FIND_ALL) != 0) { 156 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_find_similar(diff, &o.findopts), "finding renames and copies", null); 157 } 158 } 159 160 /** Generate simple output using libgit2 display helper. */ 161 162 if (!o.output) { 163 o.output = .OUTPUT_DIFF; 164 } 165 166 if (o.output != .OUTPUT_DIFF) { 167 .diff_print_stats(diff, &o); 168 } 169 170 if ((o.output & .OUTPUT_DIFF) != 0) { 171 if (o.color >= 0) { 172 core.stdc.stdio.fputs(.colors[0], core.stdc.stdio.stdout); 173 } 174 175 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_print(diff, o.format, &.color_printer, &o.color), "displaying diff", null); 176 177 if (o.color >= 0) { 178 core.stdc.stdio.fputs(.colors[0], core.stdc.stdio.stdout); 179 } 180 } 181 182 /** Cleanup before exiting. */ 183 libgit2_d.diff.git_diff_free(diff); 184 libgit2_d.tree.git_tree_free(t1); 185 libgit2_d.tree.git_tree_free(t2); 186 187 return 0; 188 } 189 190 nothrow @nogc 191 private void compute_diff_no_index(libgit2_d.diff.git_diff** diff, .diff_options* o) 192 193 in 194 { 195 } 196 197 do 198 { 199 if ((!o.treeish1) || (!o.treeish2)) { 200 .usage("two files should be provided as arguments", null); 201 } 202 203 char* file1_str = libgit2_d.example.common.read_file(o.treeish1); 204 205 if (file1_str == null) { 206 .usage("file cannot be read", o.treeish1); 207 } 208 209 char* file2_str = libgit2_d.example.common.read_file(o.treeish2); 210 211 if (file2_str == null) { 212 .usage("file cannot be read", o.treeish2); 213 } 214 215 libgit2_d.patch.git_patch* patch = null; 216 libgit2_d.example.common.check_lg2(libgit2_d.patch.git_patch_from_buffers(&patch, file1_str, core.stdc..string.strlen(file1_str), o.treeish1, file2_str, core.stdc..string.strlen(file2_str), o.treeish2, &o.diffopts), "patch buffers", null); 217 libgit2_d.buffer.git_buf buf = libgit2_d.buffer.git_buf.init; 218 libgit2_d.example.common.check_lg2(libgit2_d.patch.git_patch_to_buf(&buf, patch), "patch to buf", null); 219 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_from_buffer(diff, buf.ptr_, buf.size), "diff from patch", null); 220 libgit2_d.patch.git_patch_free(patch); 221 libgit2_d.buffer.git_buf_dispose(&buf); 222 core.stdc.stdlib.free(file1_str); 223 core.stdc.stdlib.free(file2_str); 224 } 225 226 nothrow @nogc 227 private void usage(const (char)* message, const (char)* arg) 228 229 in 230 { 231 } 232 233 do 234 { 235 if ((message != null) && (arg != null)) { 236 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s: %s\n", message, arg); 237 } else if (message != null) { 238 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s\n", message); 239 } 240 241 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n"); 242 core.stdc.stdlib.exit(1); 243 } 244 245 /** 246 * This implements very rudimentary colorized output. 247 */ 248 extern (C) 249 nothrow @nogc 250 private int color_printer(const (libgit2_d.diff.git_diff_delta)* delta, const (libgit2_d.diff.git_diff_hunk)* hunk, const (libgit2_d.diff.git_diff_line)* line, void* data) 251 252 in 253 { 254 } 255 256 do 257 { 258 int* last_color = cast(int*)(data); 259 int color = 0; 260 261 //cast(void)(delta); 262 //cast(void)(hunk); 263 264 if (*last_color >= 0) { 265 switch (line.origin) { 266 case libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_ADDITION: 267 color = 3; 268 269 break; 270 271 case libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_DELETION: 272 color = 2; 273 274 break; 275 276 case libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_ADD_EOFNL: 277 color = 3; 278 279 break; 280 281 case libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_DEL_EOFNL: 282 color = 2; 283 284 break; 285 286 case libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_FILE_HDR: 287 color = 1; 288 289 break; 290 291 case libgit2_d.diff.git_diff_line_t.GIT_DIFF_LINE_HUNK_HDR: 292 color = 4; 293 294 break; 295 296 default: 297 break; 298 } 299 300 if (color != *last_color) { 301 if ((*last_color == 1) || (color == 1)) { 302 core.stdc.stdio.fputs(.colors[0], core.stdc.stdio.stdout); 303 } 304 305 core.stdc.stdio.fputs(.colors[color], core.stdc.stdio.stdout); 306 *last_color = color; 307 } 308 } 309 310 return libgit2_d.example.common.diff_output(delta, hunk, line, cast(void*)(core.stdc.stdio.stdout)); 311 } 312 313 /** 314 * Parse arguments as copied from git-diff. 315 */ 316 nothrow @nogc 317 private void parse_opts(.diff_options* o, int argc, char** argv) 318 319 in 320 { 321 } 322 323 do 324 { 325 libgit2_d.example.args.args_info args = libgit2_d.example.args.ARGS_INFO_INIT(argc, argv); 326 327 for (args.pos = 1; args.pos < argc; ++args.pos) { 328 const (char)* a = argv[args.pos]; 329 330 if (a[0] != '-') { 331 if (o.treeish1 == null) { 332 o.treeish1 = a; 333 } else if (o.treeish2 == null) { 334 o.treeish2 = a; 335 } else { 336 .usage("Only one or two tree identifiers can be provided", null); 337 } 338 } else if ((!core.stdc..string.strcmp(a, "-p")) || (!core.stdc..string.strcmp(a, "-u")) || (!core.stdc..string.strcmp(a, "--patch"))) { 339 o.output |= .OUTPUT_DIFF; 340 o.format = libgit2_d.diff.git_diff_format_t.GIT_DIFF_FORMAT_PATCH; 341 } else if (!core.stdc..string.strcmp(a, "--cached")) { 342 o.cache = .CACHE_ONLY; 343 344 if (o.no_index >= 0) { 345 .usage("--cached and --no-index are incompatible", null); 346 } 347 } else if (!core.stdc..string.strcmp(a, "--nocache")) { 348 o.cache = .CACHE_NONE; 349 } else if ((!core.stdc..string.strcmp(a, "--name-only")) || (!core.stdc..string.strcmp(a, "--format=name"))) { 350 o.format = libgit2_d.diff.git_diff_format_t.GIT_DIFF_FORMAT_NAME_ONLY; 351 } else if ((!core.stdc..string.strcmp(a, "--name-status")) || (!core.stdc..string.strcmp(a, "--format=name-status"))) { 352 o.format = libgit2_d.diff.git_diff_format_t.GIT_DIFF_FORMAT_NAME_STATUS; 353 } else if ((!core.stdc..string.strcmp(a, "--raw")) || (!core.stdc..string.strcmp(a, "--format=raw"))) { 354 o.format = libgit2_d.diff.git_diff_format_t.GIT_DIFF_FORMAT_RAW; 355 } else if (!core.stdc..string.strcmp(a, "--format=diff-index")) { 356 o.format = libgit2_d.diff.git_diff_format_t.GIT_DIFF_FORMAT_RAW; 357 o.diffopts.id_abbrev = 40; 358 } else if (!core.stdc..string.strcmp(a, "--no-index")) { 359 o.no_index = 0; 360 361 if (o.cache == .CACHE_ONLY) { 362 .usage("--cached and --no-index are incompatible", null); 363 } 364 } else if (!core.stdc..string.strcmp(a, "--color")) { 365 o.color = 0; 366 } else if (!core.stdc..string.strcmp(a, "--no-color")) { 367 o.color = -1; 368 } else if (!core.stdc..string.strcmp(a, "-R")) { 369 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_REVERSE; 370 } else if ((!core.stdc..string.strcmp(a, "-a")) || (!core.stdc..string.strcmp(a, "--text"))) { 371 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_FORCE_TEXT; 372 } else if (!core.stdc..string.strcmp(a, "--ignore-space-at-eol")) { 373 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_IGNORE_WHITESPACE_EOL; 374 } else if ((!core.stdc..string.strcmp(a, "-b")) || (!core.stdc..string.strcmp(a, "--ignore-space-change"))) { 375 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_IGNORE_WHITESPACE_CHANGE; 376 } else if ((!core.stdc..string.strcmp(a, "-w")) || (!core.stdc..string.strcmp(a, "--ignore-all-space"))) { 377 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_IGNORE_WHITESPACE; 378 } else if (!core.stdc..string.strcmp(a, "--ignored")) { 379 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_INCLUDE_IGNORED; 380 } else if (!core.stdc..string.strcmp(a, "--untracked")) { 381 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_INCLUDE_UNTRACKED; 382 } else if (!core.stdc..string.strcmp(a, "--patience")) { 383 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_PATIENCE; 384 } else if (!core.stdc..string.strcmp(a, "--minimal")) { 385 o.diffopts.flags |= libgit2_d.diff.git_diff_option_t.GIT_DIFF_MINIMAL; 386 } else if (!core.stdc..string.strcmp(a, "--stat")) { 387 o.output |= .OUTPUT_STAT; 388 } else if (!core.stdc..string.strcmp(a, "--numstat")) { 389 o.output |= .OUTPUT_NUMSTAT; 390 } else if (!core.stdc..string.strcmp(a, "--shortstat")) { 391 o.output |= .OUTPUT_SHORTSTAT; 392 } else if (!core.stdc..string.strcmp(a, "--summary")) { 393 o.output |= .OUTPUT_SUMMARY; 394 } else if (libgit2_d.example.args.match_uint16_arg(&o.findopts.rename_threshold, &args, "-M") || libgit2_d.example.args.match_uint16_arg(&o.findopts.rename_threshold, &args, "--find-renames")) { 395 o.findopts.flags |= libgit2_d.diff.git_diff_find_t.GIT_DIFF_FIND_RENAMES; 396 } else if (libgit2_d.example.args.match_uint16_arg(&o.findopts.copy_threshold, &args, "-C") || libgit2_d.example.args.match_uint16_arg(&o.findopts.copy_threshold, &args, "--find-copies")) { 397 o.findopts.flags |= libgit2_d.diff.git_diff_find_t.GIT_DIFF_FIND_COPIES; 398 } else if (!core.stdc..string.strcmp(a, "--find-copies-harder")) { 399 o.findopts.flags |= libgit2_d.diff.git_diff_find_t.GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; 400 } else if (libgit2_d.example.args.is_prefixed(a, "-B") || libgit2_d.example.args.is_prefixed(a, "--break-rewrites")) { 401 /* TODO: parse thresholds */ 402 o.findopts.flags |= libgit2_d.diff.git_diff_find_t.GIT_DIFF_FIND_REWRITES; 403 } else if ((!libgit2_d.example.args.match_uint32_arg(&o.diffopts.context_lines, &args, "-U")) && (!libgit2_d.example.args.match_uint32_arg(&o.diffopts.context_lines, &args, "--unified")) && (!libgit2_d.example.args.match_uint32_arg(&o.diffopts.interhunk_lines, &args, "--inter-hunk-context")) && !libgit2_d.example.args.match_uint16_arg(&o.diffopts.id_abbrev, &args, "--abbrev") && !libgit2_d.example.args.match_str_arg(&o.diffopts.old_prefix, &args, "--src-prefix") && (!libgit2_d.example.args.match_str_arg(&o.diffopts.new_prefix, &args, "--dst-prefix")) && (!libgit2_d.example.args.match_str_arg(&o.dir, &args, "--git-dir"))) { 404 .usage("Unknown command line argument", a); 405 } 406 } 407 } 408 409 /** 410 * Display diff output with "--stat", "--numstat", or "--shortstat" 411 */ 412 nothrow @nogc 413 private void diff_print_stats(libgit2_d.diff.git_diff* diff, .diff_options* o) 414 415 in 416 { 417 } 418 419 do 420 { 421 libgit2_d.diff.git_diff_stats* stats; 422 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_get_stats(&stats, diff), "generating stats for diff", null); 423 424 libgit2_d.diff.git_diff_stats_format_t format = libgit2_d.diff.git_diff_stats_format_t.GIT_DIFF_STATS_NONE; 425 426 if (o.output & .OUTPUT_STAT) { 427 format |= libgit2_d.diff.git_diff_stats_format_t.GIT_DIFF_STATS_FULL; 428 } 429 430 if (o.output & .OUTPUT_SHORTSTAT) { 431 format |= libgit2_d.diff.git_diff_stats_format_t.GIT_DIFF_STATS_SHORT; 432 } 433 434 if (o.output & .OUTPUT_NUMSTAT) { 435 format |= libgit2_d.diff.git_diff_stats_format_t.GIT_DIFF_STATS_NUMBER; 436 } 437 438 if (o.output & .OUTPUT_SUMMARY) { 439 format |= libgit2_d.diff.git_diff_stats_format_t.GIT_DIFF_STATS_INCLUDE_SUMMARY; 440 } 441 442 libgit2_d.buffer.git_buf b; 443 libgit2_d.example.common.check_lg2(libgit2_d.diff.git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", null); 444 445 b = libgit2_d.buffer.GIT_BUF_INIT_CONST(null, 0); 446 core.stdc.stdio.fputs(b.ptr_, core.stdc.stdio.stdout); 447 448 libgit2_d.buffer.git_buf_dispose(&b); 449 libgit2_d.diff.git_diff_stats_free(stats); 450 }