/* * libgit2 "diff" example - shows how to use the diff API * * Written by the libgit2 contributors * * To the extent possible under law, the author(s) have dedicated all copyright * and related and neighboring rights to this software to the public domain * worldwide. This software is distributed without any warranty. * * You should have received a copy of the CC0 Public Domain Dedication along * with this software. If not, see * . */ #include "common.h" /** * This example demonstrates the use of the libgit2 diff APIs to * create `git_diff` objects and display them, emulating a number of * core Git `diff` command line options. * * This covers on a portion of the core Git diff options and doesn't * have particularly good error handling, but it should show most of * the core libgit2 diff APIs, including various types of diffs and * how to do renaming detection and patch formatting. */ static const char *colors[] = { "\033[m", /* reset */ "\033[1m", /* bold */ "\033[31m", /* red */ "\033[32m", /* green */ "\033[36m" /* cyan */ }; enum { OUTPUT_DIFF = (1 << 0), OUTPUT_STAT = (1 << 1), OUTPUT_SHORTSTAT = (1 << 2), OUTPUT_NUMSTAT = (1 << 3), OUTPUT_SUMMARY = (1 << 4) }; enum { CACHE_NORMAL = 0, CACHE_ONLY = 1, CACHE_NONE = 2 }; /** The 'opts' struct captures all the various parsed command line options. */ struct opts { git_diff_options diffopts; git_diff_find_options findopts; int color; int no_index; int cache; int output; git_diff_format_t format; const char *treeish1; const char *treeish2; const char *dir; }; /** These functions are implemented at the end */ static void usage(const char *message, const char *arg); static void parse_opts(struct opts *o, int argc, char *argv[]); static int color_printer( const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); static void diff_print_stats(git_diff *diff, struct opts *o); static void compute_diff_no_index(git_diff **diff, struct opts *o); int lg2_diff(git_repository *repo, int argc, char *argv[]) { git_tree *t1 = NULL, *t2 = NULL; git_diff *diff; struct opts o = { GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT, -1, -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." }; parse_opts(&o, argc, argv); /** * Possible argument patterns: * * * <sha1> <sha2> * * <sha1> --cached * * <sha1> * * --cached * * --nocache (don't use index data in diff at all) * * --no-index <file1> <file2> * * nothing * * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> * are not supported in this example */ if (o.no_index >= 0) { compute_diff_no_index(&diff, &o); } else { if (o.treeish1) treeish_to_tree(&t1, repo, o.treeish1); if (o.treeish2) treeish_to_tree(&t2, repo, o.treeish2); if (t1 && t2) check_lg2( git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts), "diff trees", NULL); else if (o.cache != CACHE_NORMAL) { if (!t1) treeish_to_tree(&t1, repo, "HEAD"); if (o.cache == CACHE_NONE) check_lg2( git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts), "diff tree to working directory", NULL); else check_lg2( git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), "diff tree to index", NULL); } else if (t1) check_lg2( git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts), "diff tree to working directory", NULL); else check_lg2( git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts), "diff index to working directory", NULL); /** Apply rename and copy detection if requested. */ if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0) check_lg2( git_diff_find_similar(diff, &o.findopts), "finding renames and copies", NULL); } /** Generate simple output using libgit2 display helper. */ if (!o.output) o.output = OUTPUT_DIFF; if (o.output != OUTPUT_DIFF) diff_print_stats(diff, &o); if ((o.output & OUTPUT_DIFF) != 0) { if (o.color >= 0) fputs(colors[0], stdout); check_lg2( git_diff_print(diff, o.format, color_printer, &o.color), "displaying diff", NULL); if (o.color >= 0) fputs(colors[0], stdout); } /** Cleanup before exiting. */ git_diff_free(diff); git_tree_free(t1); git_tree_free(t2); return 0; } static void compute_diff_no_index(git_diff **diff, struct opts *o) { git_patch *patch = NULL; char *file1_str = NULL; char *file2_str = NULL; git_buf buf = {0}; if (!o->treeish1 || !o->treeish2) { usage("two files should be provided as arguments", NULL); } file1_str = read_file(o->treeish1); if (file1_str == NULL) { usage("file cannot be read", o->treeish1); } file2_str = read_file(o->treeish2); if (file2_str == NULL) { usage("file cannot be read", o->treeish2); } check_lg2( git_patch_from_buffers(&patch, file1_str, strlen(file1_str), o->treeish1, file2_str, strlen(file2_str), o->treeish2, &o->diffopts), "patch buffers", NULL); check_lg2( git_patch_to_buf(&buf, patch), "patch to buf", NULL); check_lg2( git_diff_from_buffer(diff, buf.ptr, buf.size), "diff from patch", NULL); git_patch_free(patch); git_buf_dispose(&buf); free(file1_str); free(file2_str); } static void usage(const char *message, const char *arg) { if (message && arg) fprintf(stderr, "%s: %s\n", message, arg); else if (message) fprintf(stderr, "%s\n", message); fprintf(stderr, "usage: diff [ []]\n"); exit(1); } /** This implements very rudimentary colorized output. */ static int color_printer( const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *data) { int *last_color = data, color = 0; (void)delta; (void)hunk; if (*last_color >= 0) { switch (line->origin) { case GIT_DIFF_LINE_ADDITION: color = 3; break; case GIT_DIFF_LINE_DELETION: color = 2; break; case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; case GIT_DIFF_LINE_FILE_HDR: color = 1; break; case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; default: break; } if (color != *last_color) { if (*last_color == 1 || color == 1) fputs(colors[0], stdout); fputs(colors[color], stdout); *last_color = color; } } return diff_output(delta, hunk, line, stdout); } /** Parse arguments as copied from git-diff. */ static void parse_opts(struct opts *o, int argc, char *argv[]) { struct args_info args = ARGS_INFO_INIT; for (args.pos = 1; args.pos < argc; ++args.pos) { const char *a = argv[args.pos]; if (a[0] != '-') { if (o->treeish1 == NULL) o->treeish1 = a; else if (o->treeish2 == NULL) o->treeish2 = a; else usage("Only one or two tree identifiers can be provided", NULL); } else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) { o->output |= OUTPUT_DIFF; o->format = GIT_DIFF_FORMAT_PATCH; } else if (!strcmp(a, "--cached")) { o->cache = CACHE_ONLY; if (o->no_index >= 0) usage("--cached and --no-index are incompatible", NULL); } else if (!strcmp(a, "--nocache")) o->cache = CACHE_NONE; else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name")) o->format = GIT_DIFF_FORMAT_NAME_ONLY; else if (!strcmp(a, "--name-status") || !strcmp(a, "--format=name-status")) o->format = GIT_DIFF_FORMAT_NAME_STATUS; else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw")) o->format = GIT_DIFF_FORMAT_RAW; else if (!strcmp(a, "--format=diff-index")) { o->format = GIT_DIFF_FORMAT_RAW; o->diffopts.id_abbrev = 40; } else if (!strcmp(a, "--no-index")) { o->no_index = 0; if (o->cache == CACHE_ONLY) usage("--cached and --no-index are incompatible", NULL); } else if (!strcmp(a, "--color")) o->color = 0; else if (!strcmp(a, "--no-color")) o->color = -1; else if (!strcmp(a, "-R")) o->diffopts.flags |= GIT_DIFF_REVERSE; else if (!strcmp(a, "-a") || !strcmp(a, "--text")) o->diffopts.flags |= GIT_DIFF_FORCE_TEXT; else if (!strcmp(a, "--ignore-space-at-eol")) o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE; else if (!strcmp(a, "--ignored")) o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED; else if (!strcmp(a, "--untracked")) o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; else if (!strcmp(a, "--patience")) o->diffopts.flags |= GIT_DIFF_PATIENCE; else if (!strcmp(a, "--minimal")) o->diffopts.flags |= GIT_DIFF_MINIMAL; else if (!strcmp(a, "--stat")) o->output |= OUTPUT_STAT; else if (!strcmp(a, "--numstat")) o->output |= OUTPUT_NUMSTAT; else if (!strcmp(a, "--shortstat")) o->output |= OUTPUT_SHORTSTAT; else if (!strcmp(a, "--summary")) o->output |= OUTPUT_SUMMARY; else if (match_uint16_arg( &o->findopts.rename_threshold, &args, "-M") || match_uint16_arg( &o->findopts.rename_threshold, &args, "--find-renames")) o->findopts.flags |= GIT_DIFF_FIND_RENAMES; else if (match_uint16_arg( &o->findopts.copy_threshold, &args, "-C") || match_uint16_arg( &o->findopts.copy_threshold, &args, "--find-copies")) o->findopts.flags |= GIT_DIFF_FIND_COPIES; else if (!strcmp(a, "--find-copies-harder")) o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) /* TODO: parse thresholds */ o->findopts.flags |= GIT_DIFF_FIND_REWRITES; else if (!match_uint32_arg( &o->diffopts.context_lines, &args, "-U") && !match_uint32_arg( &o->diffopts.context_lines, &args, "--unified") && !match_uint32_arg( &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") && !match_uint16_arg( &o->diffopts.id_abbrev, &args, "--abbrev") && !match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") && !match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") && !match_str_arg(&o->dir, &args, "--git-dir")) usage("Unknown command line argument", a); } } /** Display diff output with "--stat", "--numstat", or "--shortstat" */ static void diff_print_stats(git_diff *diff, struct opts *o) { git_diff_stats *stats; git_buf b = GIT_BUF_INIT_CONST(NULL, 0); git_diff_stats_format_t format = 0; check_lg2( git_diff_get_stats(&stats, diff), "generating stats for diff", NULL); if (o->output & OUTPUT_STAT) format |= GIT_DIFF_STATS_FULL; if (o->output & OUTPUT_SHORTSTAT) format |= GIT_DIFF_STATS_SHORT; if (o->output & OUTPUT_NUMSTAT) format |= GIT_DIFF_STATS_NUMBER; if (o->output & OUTPUT_SUMMARY) format |= GIT_DIFF_STATS_INCLUDE_SUMMARY; check_lg2( git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL); fputs(b.ptr, stdout); git_buf_dispose(&b); git_diff_stats_free(stats); }