diff options
Diffstat (limited to 'examples/diff.c')
-rw-r--r-- | examples/diff.c | 414 |
1 files changed, 203 insertions, 211 deletions
diff --git a/examples/diff.c b/examples/diff.c index 11efa21ec..daf5d7030 100644 --- a/examples/diff.c +++ b/examples/diff.c @@ -1,70 +1,176 @@ -#include <stdio.h> -#include <git2.h> -#include <stdlib.h> -#include <string.h> +/* + * 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 + * <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#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 */ +}; -static void check(int error, const char *message) -{ - if (error) { - fprintf(stderr, "%s (%d)\n", message, error); - exit(1); - } -} +/** 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 cached; + git_diff_format_t format; + const char *treeish1; + const char *treeish2; + const char *dir; +}; -static int resolve_to_tree( - git_repository *repo, const char *identifier, git_tree **tree) +/** These functions are implemented at the end */ +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*); + +int main(int argc, char *argv[]) { - int err = 0; - git_object *obj = NULL; - - if ((err = git_revparse_single(&obj, repo, identifier)) < 0) - return err; - - switch (git_object_type(obj)) { - case GIT_OBJ_TREE: - *tree = (git_tree *)obj; - break; - case GIT_OBJ_COMMIT: - err = git_commit_tree(tree, (git_commit *)obj); - git_object_free(obj); - break; - default: - err = GIT_ENOTFOUND; + git_repository *repo = NULL; + git_tree *t1 = NULL, *t2 = NULL; + git_diff *diff; + struct opts o = { + GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT, + -1, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." + }; + + git_threads_init(); + + parse_opts(&o, argc, argv); + + check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL), + "Could not open repository", o.dir); + + /** + * Possible argument patterns: + * + * * <sha1> <sha2> + * * <sha1> --cached + * * <sha1> + * * --cached + * * nothing + * + * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> + * are not supported in this example + */ + + 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 (t1 && o.cached) + 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 if (o.cached) { + treeish_to_tree(&t1, repo, "HEAD"); + check_lg2( + git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), + "diff tree to index", 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.color >= 0) + fputs(colors[0], stdout); + + check_lg2( + git_diff_print(diff, o.format, color_printer, &o.color), + "displaying diff", NULL); - return err; + if (o.color >= 0) + fputs(colors[0], stdout); + + /** Cleanup before exiting. */ + + git_diff_free(diff); + git_tree_free(t1); + git_tree_free(t2); + git_repository_free(repo); + + git_threads_shutdown(); + + return 0; } -char *colors[] = { - "\033[m", /* reset */ - "\033[1m", /* bold */ - "\033[31m", /* red */ - "\033[32m", /* green */ - "\033[36m" /* cyan */ -}; +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 [<tree-oid> [<tree-oid>]]\n"); + exit(1); +} -static int printer( +/** This implements very rudimentary colorized output. */ +static int color_printer( const git_diff_delta *delta, - const git_diff_range *range, - char usage, - const char *line, - size_t line_len, + const git_diff_hunk *hunk, + const git_diff_line *line, void *data) { int *last_color = data, color = 0; - (void)delta; (void)range; (void)line_len; + (void)delta; (void)hunk; if (*last_color >= 0) { - switch (usage) { - case GIT_DIFF_LINE_ADDITION: color = 3; break; - case GIT_DIFF_LINE_DELETION: color = 2; break; + 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: color = 0; + 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); @@ -73,193 +179,79 @@ static int printer( } } - fputs(line, stdout); - return 0; -} - -static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) -{ - size_t len = strlen(pattern); - uint16_t strval; - char *endptr = NULL; - if (strncmp(arg, pattern, len)) - return 0; - if (arg[len] == '\0' && pattern[len - 1] != '=') - return 1; - if (arg[len] == '=') - len++; - strval = strtoul(arg + len, &endptr, 0); - if (endptr == arg) - return 0; - *val = strval; - return 1; -} - -static int check_str_param(const char *arg, const char *pattern, const char **val) -{ - size_t len = strlen(pattern); - if (strncmp(arg, pattern, len)) - return 0; - *val = (const char *)(arg + len); - return 1; -} - -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 [<tree-oid> [<tree-oid>]]\n"); - exit(1); + return diff_output(delta, hunk, line, stdout); } -enum { - FORMAT_PATCH = 0, - FORMAT_COMPACT = 1, - FORMAT_RAW = 2 -}; - -int main(int argc, char *argv[]) +/** Parse arguments as copied from git-diff. */ +static void parse_opts(struct opts *o, int argc, char *argv[]) { - git_repository *repo = NULL; - git_tree *t1 = NULL, *t2 = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - git_diff_list *diff; - int i, color = -1, format = FORMAT_PATCH, cached = 0; - char *a, *treeish1 = NULL, *treeish2 = NULL; - const char *dir = "."; + struct args_info args = ARGS_INFO_INIT; - git_threads_init(); - - /* parse arguments as copied from git-diff */ - for (i = 1; i < argc; ++i) { - a = argv[i]; + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; if (a[0] != '-') { - if (treeish1 == NULL) - treeish1 = a; - else if (treeish2 == NULL) - treeish2 = a; + 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")) - format = FORMAT_PATCH; + o->format = GIT_DIFF_FORMAT_PATCH; else if (!strcmp(a, "--cached")) - cached = 1; + o->cached = 1; + else if (!strcmp(a, "--name-only")) + o->format = GIT_DIFF_FORMAT_NAME_ONLY; else if (!strcmp(a, "--name-status")) - format = FORMAT_COMPACT; + o->format = GIT_DIFF_FORMAT_NAME_STATUS; else if (!strcmp(a, "--raw")) - format = FORMAT_RAW; + o->format = GIT_DIFF_FORMAT_RAW; else if (!strcmp(a, "--color")) - color = 0; + o->color = 0; else if (!strcmp(a, "--no-color")) - color = -1; + o->color = -1; else if (!strcmp(a, "-R")) - opts.flags |= GIT_DIFF_REVERSE; + o->diffopts.flags |= GIT_DIFF_REVERSE; else if (!strcmp(a, "-a") || !strcmp(a, "--text")) - opts.flags |= GIT_DIFF_FORCE_TEXT; + o->diffopts.flags |= GIT_DIFF_FORCE_TEXT; else if (!strcmp(a, "--ignore-space-at-eol")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE; else if (!strcmp(a, "--ignored")) - opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED; else if (!strcmp(a, "--untracked")) - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - else if (check_uint16_param(a, "-M", &findopts.rename_threshold) || - check_uint16_param(a, "--find-renames", - &findopts.rename_threshold)) - findopts.flags |= GIT_DIFF_FIND_RENAMES; - else if (check_uint16_param(a, "-C", &findopts.copy_threshold) || - check_uint16_param(a, "--find-copies", - &findopts.copy_threshold)) - findopts.flags |= GIT_DIFF_FIND_COPIES; + o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + 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")) - findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; - else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) { + o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) /* TODO: parse thresholds */ - findopts.flags |= GIT_DIFF_FIND_REWRITES; - } - else if (!check_uint16_param(a, "-U", &opts.context_lines) && - !check_uint16_param(a, "--unified=", &opts.context_lines) && - !check_uint16_param(a, "--inter-hunk-context=", - &opts.interhunk_lines) && - !check_str_param(a, "--src-prefix=", &opts.old_prefix) && - !check_str_param(a, "--dst-prefix=", &opts.new_prefix) && - !check_str_param(a, "--git-dir=", &dir)) - usage("Unknown arg", a); + o->findopts.flags |= GIT_DIFF_FIND_REWRITES; + else if (!match_uint16_arg( + &o->diffopts.context_lines, &args, "-U") && + !match_uint16_arg( + &o->diffopts.context_lines, &args, "--unified") && + !match_uint16_arg( + &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") && + !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); } - - /* open repo */ - - check(git_repository_open_ext(&repo, dir, 0, NULL), - "Could not open repository"); - - if (treeish1) - check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree"); - if (treeish2) - check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree"); - - /* <sha1> <sha2> */ - /* <sha1> --cached */ - /* <sha1> */ - /* --cached */ - /* nothing */ - - if (t1 && t2) - check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff"); - else if (t1 && cached) - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - else if (t1) { - git_diff_list *diff2; - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - check(git_diff_index_to_workdir(&diff2, repo, NULL, &opts), "Diff"); - check(git_diff_merge(diff, diff2), "Merge diffs"); - git_diff_list_free(diff2); - } - else if (cached) { - check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD"); - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - } - else - check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff"); - - if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0) - check(git_diff_find_similar(diff, &findopts), - "finding renames and copies "); - - if (color >= 0) - fputs(colors[0], stdout); - - switch (format) { - case FORMAT_PATCH: - check(git_diff_print_patch(diff, printer, &color), "Displaying diff"); - break; - case FORMAT_COMPACT: - check(git_diff_print_compact(diff, printer, &color), "Displaying diff"); - break; - case FORMAT_RAW: - check(git_diff_print_raw(diff, printer, &color), "Displaying diff"); - break; - } - - if (color >= 0) - fputs(colors[0], stdout); - - git_diff_list_free(diff); - git_tree_free(t1); - git_tree_free(t2); - git_repository_free(repo); - - git_threads_shutdown(); - - return 0; } - |