From a2e895be820a2fd77285ef4576afe53f68c96ca2 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 7 Feb 2012 12:14:28 -0800 Subject: [PATCH] Continue implementation of git-diff * Implemented git_diff_index_to_tree * Reworked git_diff_options structure to handle more options * Made most of the options in git_diff_options actually work * Reorganized code a bit to remove some redundancy * Added option parsing to examples/diff.c to test most options --- examples/Makefile | 5 +- examples/diff.c | 117 ++++++-- include/git2/diff.h | 34 ++- src/diff.c | 494 ++++++++++++++++++++++----------- src/diff.h | 5 +- tests-clar/diff/blob.c | 5 +- tests-clar/diff/diff_helpers.c | 20 ++ tests-clar/diff/diff_helpers.h | 1 + tests-clar/diff/index.c | 96 +++++++ tests-clar/diff/tree.c | 29 +- 10 files changed, 589 insertions(+), 217 deletions(-) create mode 100644 tests-clar/diff/index.c diff --git a/examples/Makefile b/examples/Makefile index 156a5ba6d..fe99c75cb 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,12 +3,13 @@ CC = gcc CFLAGS = -g -I../include -I../src LFLAGS = -L../build -lgit2 -lz +APPS = general showindex diff -all: general showindex diff +all: $(APPS) % : %.c $(CC) -o $@ $(CFLAGS) $< $(LFLAGS) clean: - $(RM) general showindex diff + $(RM) $(APPS) $(RM) -r *.dSYM diff --git a/examples/diff.c b/examples/diff.c index 9b696dad5..5eb0f3179 100644 --- a/examples/diff.c +++ b/examples/diff.c @@ -87,46 +87,123 @@ int printer(void *data, char usage, const char *line) return 0; } +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; + strval = strtoul(arg + len, &endptr, 0); + if (endptr == arg) + return 0; + *val = strval; + return 1; +} + +int check_str_param(const char *arg, const char *pattern, char **val) +{ + size_t len = strlen(pattern); + if (strncmp(arg, pattern, len)) + return 0; + *val = (char *)(arg + len); + return 1; +} + +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); +} + int main(int argc, char *argv[]) { char path[GIT_PATH_MAX]; git_repository *repo = NULL; - git_tree *a, *b; + git_tree *t1 = NULL, *t2 = NULL; git_diff_options opts = {0}; git_diff_list *diff; - char *dir = "."; - int color = -1; + int i, color = -1, compact = 0; + char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL; - if (argc != 3) { - fprintf(stderr, "usage: diff \n"); - exit(1); + /* parse arguments as copied from git-diff */ + + for (i = 1; i < argc; ++i) { + a = argv[i]; + + if (a[0] != '-') { + if (treeish1 == NULL) + treeish1 = a; + else if (treeish2 == NULL) + 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")) + compact = 0; + else if (!strcmp(a, "--name-status")) + compact = 1; + else if (!strcmp(a, "--color")) + color = 0; + else if (!strcmp(a, "--no-color")) + color = -1; + else if (!strcmp(a, "-R")) + opts.flags |= GIT_DIFF_REVERSE; + else if (!strcmp(a, "-a") || !strcmp(a, "--text")) + opts.flags |= GIT_DIFF_FORCE_TEXT; + else if (!strcmp(a, "--ignore-space-at-eol")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) + opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + 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.src_prefix) && + !check_str_param(a, "--dst-prefix=", &opts.dst_prefix)) + usage("Unknown arg", a); } + if (!treeish1) + usage("Must provide at least one tree identifier (for now)", NULL); + + /* open repo */ + check(git_repository_discover(path, sizeof(path), dir, 0, "/"), "Could not discover repository"); check(git_repository_open(&repo, path), "Could not open repository"); - check(resolve_to_tree(repo, argv[1], &a), "Looking up first tree"); - check(resolve_to_tree(repo, argv[2], &b), "Looking up second tree"); + check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree"); + if (treeish2) + check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree"); - check(git_diff_tree_to_tree(repo, &opts, a, b, &diff), "Generating diff"); + if (!treeish2) + check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Generating diff"); + else + check(git_diff_tree_to_tree(repo, &opts, t1, t2, &diff), "Generating diff"); - fputs(colors[0], stdout); + if (color >= 0) + fputs(colors[0], stdout); - check(git_diff_print_compact(diff, &color, printer), "Displaying diff summary"); + if (compact) + check(git_diff_print_compact(diff, &color, printer), "Displaying diff summary"); + else + check(git_diff_print_patch(diff, &color, printer), "Displaying diff"); - fprintf(stdout, "--\n"); - - color = 0; - - check(git_diff_print_patch(diff, &color, printer), "Displaying diff"); - - fputs(colors[0], stdout); + if (color >= 0) + fputs(colors[0], stdout); git_diff_list_free(diff); - git_tree_free(a); - git_tree_free(b); + git_tree_free(t1); + git_tree_free(t2); git_repository_free(repo); return 0; diff --git a/include/git2/diff.h b/include/git2/diff.h index 56801ca01..e9ef5c356 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -29,17 +29,33 @@ */ GIT_BEGIN_DECL +enum { + GIT_DIFF_NORMAL = 0, + GIT_DIFF_REVERSE = (1 << 0), + GIT_DIFF_FORCE_TEXT = (1 << 1), + GIT_DIFF_IGNORE_WHITESPACE = (1 << 2), + GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3), + GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4), + GIT_DIFF_IGNORE_SUBMODULES = (1 << 5), + GIT_DIFF_PATIENCE = (1 << 6) +}; + /** * Structure describing options about how the diff should be executed. * + * Setting all values of the structure to zero will yield the default + * values. Similarly, passing NULL for the options structure will + * give the defaults. The default values are marked below. + * * @todo Most of the parameters here are not actually supported at this time. */ typedef struct { - int context_lines; - int interhunk_lines; - int ignore_whitespace; - int force_text; /**< generate text diffs even for binaries */ - git_strarray pathspec; + uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */ + uint16_t context_lines; /**< defaults to 3 */ + uint16_t interhunk_lines; /**< defaults to 3 */ + char *src_prefix; /**< defaults to "a" */ + char *dst_prefix; /**< defaults to "b" */ + git_strarray pathspec; /**< defaults to show all paths */ } git_diff_options; /** @@ -158,7 +174,7 @@ typedef int (*git_diff_output_fn)( */ GIT_EXTERN(int) git_diff_tree_to_tree( git_repository *repo, - const git_diff_options *opts, + const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old, git_tree *new, git_diff_list **diff); @@ -169,7 +185,7 @@ GIT_EXTERN(int) git_diff_tree_to_tree( */ GIT_EXTERN(int) git_diff_index_to_tree( git_repository *repo, - const git_diff_options *opts, + const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old, git_diff_list **diff); @@ -179,7 +195,7 @@ GIT_EXTERN(int) git_diff_index_to_tree( */ GIT_EXTERN(int) git_diff_workdir_to_tree( git_repository *repo, - const git_diff_options *opts, + const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old, git_diff_list **diff); @@ -189,7 +205,7 @@ GIT_EXTERN(int) git_diff_workdir_to_tree( */ GIT_EXTERN(int) git_diff_workdir_to_index( git_repository *repo, - const git_diff_options *opts, + const git_diff_options *opts, /**< can be NULL for defaults */ git_diff_list **diff); /** diff --git a/src/diff.c b/src/diff.c index a5b5e6198..9a12aa07c 100644 --- a/src/diff.c +++ b/src/diff.c @@ -12,33 +12,6 @@ #include "blob.h" #include -static git_diff_delta *file_delta_new( - git_diff_list *diff, - const git_tree_diff_data *tdiff) -{ - git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); - - if (!delta) { - git__rethrow(GIT_ENOMEM, "Could not allocate diff record"); - return NULL; - } - - /* copy shared fields */ - delta->status = tdiff->status; - delta->old_attr = tdiff->old_attr; - delta->new_attr = tdiff->new_attr; - delta->old_oid = tdiff->old_oid; - delta->new_oid = tdiff->new_oid; - delta->path = git__strdup(diff->pfx.ptr); - if (delta->path == NULL) { - git__free(delta); - git__rethrow(GIT_ENOMEM, "Could not allocate diff record path"); - return NULL; - } - - return delta; -} - static void file_delta_free(git_diff_delta *delta) { if (!delta) @@ -55,77 +28,119 @@ static void file_delta_free(git_diff_delta *delta) git__free(delta); } -static int tree_add_cb(const char *root, git_tree_entry *entry, void *data) +static int file_delta_new__from_one( + git_diff_list *diff, + git_status_t status, + unsigned int attr, + const git_oid *oid, + const char *path) +{ + int error; + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + + /* This fn is just for single-sided diffs */ + assert(status == GIT_STATUS_ADDED || status == GIT_STATUS_DELETED); + + if (!delta) + return git__rethrow(GIT_ENOMEM, "Could not allocate diff record"); + + if ((delta->path = git__strdup(path)) == NULL) { + git__free(delta); + return git__rethrow(GIT_ENOMEM, "Could not allocate diff record path"); + } + + if (diff->opts.flags & GIT_DIFF_REVERSE) + status = (status == GIT_STATUS_ADDED) ? + GIT_STATUS_DELETED : GIT_STATUS_ADDED; + + delta->status = status; + + if (status == GIT_STATUS_ADDED) { + delta->new_attr = attr; + git_oid_cpy(&delta->new_oid, oid); + } else { + delta->old_attr = attr; + git_oid_cpy(&delta->old_oid, oid); + } + + if ((error = git_vector_insert(&diff->files, delta)) < GIT_SUCCESS) + file_delta_free(delta); + + return error; +} + +static int file_delta_new__from_tree_diff( + git_diff_list *diff, + const git_tree_diff_data *tdiff) +{ + int error; + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + + if (!delta) + return git__rethrow(GIT_ENOMEM, "Could not allocate diff record"); + + if ((diff->opts.flags & GIT_DIFF_REVERSE) == 0) { + delta->status = tdiff->status; + delta->old_attr = tdiff->old_attr; + delta->new_attr = tdiff->new_attr; + delta->old_oid = tdiff->old_oid; + delta->new_oid = tdiff->new_oid; + } else { + /* reverse the polarity of the neutron flow */ + switch (tdiff->status) { + case GIT_STATUS_ADDED: delta->status = GIT_STATUS_DELETED; break; + case GIT_STATUS_DELETED: delta->status = GIT_STATUS_ADDED; break; + default: delta->status = tdiff->status; + } + delta->old_attr = tdiff->new_attr; + delta->new_attr = tdiff->old_attr; + delta->old_oid = tdiff->new_oid; + delta->new_oid = tdiff->old_oid; + } + + delta->path = git__strdup(diff->pfx.ptr); + if (delta->path == NULL) { + git__free(delta); + return git__rethrow(GIT_ENOMEM, "Could not allocate diff record path"); + } + + if ((error = git_vector_insert(&diff->files, delta)) < GIT_SUCCESS) + file_delta_free(delta); + + return error; +} + +static int tree_walk_cb(const char *root, git_tree_entry *entry, void *data) { int error; git_diff_list *diff = data; ssize_t pfx_len = diff->pfx.size; - git_tree_diff_data tdiff; - git_diff_delta *delta; - memset(&tdiff, 0, sizeof(tdiff)); - tdiff.new_attr = git_tree_entry_attributes(entry); - if (S_ISDIR(tdiff.new_attr)) + if (S_ISDIR(git_tree_entry_attributes(entry))) return GIT_SUCCESS; - git_oid_cpy(&tdiff.new_oid, git_tree_entry_id(entry)); - tdiff.status = GIT_STATUS_ADDED; - tdiff.path = git_tree_entry_name(entry); - + /* join pfx, root, and entry->filename into one */ if ((error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, root)) || - (error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, tdiff.path))) + (error = git_buf_joinpath( + &diff->pfx, diff->pfx.ptr, git_tree_entry_name(entry)))) return error; - delta = file_delta_new(diff, &tdiff); - if (delta == NULL) - error = GIT_ENOMEM; - else if ((error = git_vector_insert(&diff->files, delta)) < GIT_SUCCESS) - file_delta_free(delta); + error = file_delta_new__from_one( + diff, diff->mode, git_tree_entry_attributes(entry), + git_tree_entry_id(entry), diff->pfx.ptr); git_buf_truncate(&diff->pfx, pfx_len); return error; } -static int tree_del_cb(const char *root, git_tree_entry *entry, void *data) -{ - int error; - git_diff_list *diff = data; - ssize_t pfx_len = diff->pfx.size; - git_tree_diff_data tdiff; - git_diff_delta *delta; - - memset(&tdiff, 0, sizeof(tdiff)); - tdiff.old_attr = git_tree_entry_attributes(entry); - if (S_ISDIR(tdiff.old_attr)) - return GIT_SUCCESS; - - git_oid_cpy(&tdiff.old_oid, git_tree_entry_id(entry)); - tdiff.status = GIT_STATUS_DELETED; - tdiff.path = git_tree_entry_name(entry); - - if ((error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, root)) || - (error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, tdiff.path))) - return error; - - delta = file_delta_new(diff, &tdiff); - if (delta == NULL) - error = GIT_ENOMEM; - else if ((error = git_vector_insert(&diff->files, delta)) < GIT_SUCCESS) - file_delta_free(delta); - - git_buf_truncate(&diff->pfx, pfx_len); - - return error; -} - -static int tree_diff_cb(const git_tree_diff_data *ptr, void *data) +static int tree_diff_cb(const git_tree_diff_data *tdiff, void *data) { int error; git_diff_list *diff = data; ssize_t pfx_len = diff->pfx.size; - error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, ptr->path); + error = git_buf_joinpath(&diff->pfx, diff->pfx.ptr, tdiff->path); if (error < GIT_SUCCESS) return error; @@ -138,56 +153,83 @@ static int tree_diff_cb(const git_tree_diff_data *ptr, void *data) * and a deletion (thank you, git_tree_diff!) * otherwise, this is a blob-to-blob diff */ - if (S_ISDIR(ptr->old_attr) && S_ISDIR(ptr->new_attr)) { + if (S_ISDIR(tdiff->old_attr) && S_ISDIR(tdiff->new_attr)) { git_tree *old = NULL, *new = NULL; - if (!(error = git_tree_lookup(&old, diff->repo, &ptr->old_oid)) && - !(error = git_tree_lookup(&new, diff->repo, &ptr->new_oid))) - { + if (!(error = git_tree_lookup(&old, diff->repo, &tdiff->old_oid)) && + !(error = git_tree_lookup(&new, diff->repo, &tdiff->new_oid))) error = git_tree_diff(old, new, tree_diff_cb, diff); - } git_tree_free(old); git_tree_free(new); - } else if (S_ISDIR(ptr->old_attr) && ptr->new_attr == 0) { - /* deleted a whole tree */ - git_tree *old = NULL; - if (!(error = git_tree_lookup(&old, diff->repo, &ptr->old_oid))) { - error = git_tree_walk(old, tree_del_cb, GIT_TREEWALK_POST, diff); - git_tree_free(old); - } - } else if (S_ISDIR(ptr->new_attr) && ptr->old_attr == 0) { - /* added a whole tree */ - git_tree *new = NULL; - if (!(error = git_tree_lookup(&new, diff->repo, &ptr->new_oid))) { - error = git_tree_walk(new, tree_add_cb, GIT_TREEWALK_POST, diff); - git_tree_free(new); - } - } else { - git_diff_delta *delta = file_delta_new(diff, ptr); - if (delta == NULL) - error = GIT_ENOMEM; - else if ((error = git_vector_insert(&diff->files, delta)) < GIT_SUCCESS) - file_delta_free(delta); - } + } else if (S_ISDIR(tdiff->old_attr) || S_ISDIR(tdiff->new_attr)) { + git_tree *tree = NULL; + int added_dir = S_ISDIR(tdiff->new_attr); + const git_oid *oid = added_dir ? &tdiff->new_oid : &tdiff->old_oid; + diff->mode = added_dir ? GIT_STATUS_ADDED : GIT_STATUS_DELETED; + + if (!(error = git_tree_lookup(&tree, diff->repo, oid))) + error = git_tree_walk(tree, tree_walk_cb, GIT_TREEWALK_POST, diff); + git_tree_free(tree); + } else + error = file_delta_new__from_tree_diff(diff, tdiff); git_buf_truncate(&diff->pfx, pfx_len); return error; } +static char *git_diff_src_prefix_default = "a/"; +static char *git_diff_dst_prefix_default = "b/"; +#define PREFIX_IS_DEFAULT(A) \ + ((A) == git_diff_src_prefix_default || (A) == git_diff_dst_prefix_default) + +static char *copy_prefix(const char *prefix) +{ + size_t len = strlen(prefix); + char *str = git__malloc(len + 2); + if (str != NULL) { + memcpy(str, prefix, len + 1); + /* append '/' at end if needed */ + if (len > 0 && str[len - 1] != '/') { + str[len] = '/'; + str[len + 1] = '\0'; + } + } + return str; +} + static git_diff_list *git_diff_list_alloc( git_repository *repo, const git_diff_options *opts) { git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); - if (diff != NULL) { - if (opts != NULL) { - memcpy(&diff->opts, opts, sizeof(git_diff_options)); - /* do something safer with the pathspec strarray */ - } - diff->repo = repo; - git_buf_init(&diff->pfx, 0); + if (diff == NULL) + return NULL; + + diff->repo = repo; + git_buf_init(&diff->pfx, 0); + + if (opts == NULL) + return diff; + + memcpy(&diff->opts, opts, sizeof(git_diff_options)); + + diff->opts.src_prefix = (opts->src_prefix == NULL) ? + git_diff_src_prefix_default : copy_prefix(opts->src_prefix); + diff->opts.dst_prefix = (opts->dst_prefix == NULL) ? + git_diff_dst_prefix_default : copy_prefix(opts->dst_prefix); + if (!diff->opts.src_prefix || !diff->opts.dst_prefix) { + git__free(diff); + return NULL; } + if (diff->opts.flags & GIT_DIFF_REVERSE) { + char *swap = diff->opts.src_prefix; + diff->opts.src_prefix = diff->opts.dst_prefix; + diff->opts.dst_prefix = swap; + } + + /* do something safe with the pathspec strarray */ + return diff; } @@ -205,6 +247,14 @@ void git_diff_list_free(git_diff_list *diff) diff->files.contents[i] = NULL; } git_vector_free(&diff->files); + if (!PREFIX_IS_DEFAULT(diff->opts.src_prefix)) { + git__free(diff->opts.src_prefix); + diff->opts.src_prefix = NULL; + } + if (!PREFIX_IS_DEFAULT(diff->opts.dst_prefix)) { + git__free(diff->opts.dst_prefix); + diff->opts.dst_prefix = NULL; + } git__free(diff); } @@ -229,6 +279,111 @@ int git_diff_tree_to_tree( return error; } +typedef struct { + git_diff_list *diff; + git_index *index; + unsigned int index_pos; +} index_to_tree_info; + +static int add_new_index_deltas( + index_to_tree_info *info, + const char *stop_path) +{ + int error; + git_index_entry *idx_entry = git_index_get(info->index, info->index_pos); + + while (idx_entry != NULL && + (stop_path == NULL || strcmp(idx_entry->path, stop_path) < 0)) + { + error = file_delta_new__from_one( + info->diff, GIT_STATUS_ADDED, idx_entry->mode, + &idx_entry->oid, idx_entry->path); + if (error < GIT_SUCCESS) + return error; + + idx_entry = git_index_get(info->index, ++info->index_pos); + } + + return GIT_SUCCESS; +} + +static int diff_index_to_tree_cb(const char *root, git_tree_entry *tree_entry, void *data) +{ + int error; + index_to_tree_info *info = data; + git_index_entry *idx_entry; + + /* TODO: submodule support for GIT_OBJ_COMMITs in tree */ + if (git_tree_entry_type(tree_entry) != GIT_OBJ_BLOB) + return GIT_SUCCESS; + + error = git_buf_joinpath(&info->diff->pfx, root, git_tree_entry_name(tree_entry)); + if (error < GIT_SUCCESS) + return error; + + /* create add deltas for index entries that are not in the tree */ + error = add_new_index_deltas(info, info->diff->pfx.ptr); + if (error < GIT_SUCCESS) + return error; + + /* create delete delta for tree entries that are not in the index */ + idx_entry = git_index_get(info->index, info->index_pos); + if (idx_entry == NULL || strcmp(idx_entry->path, info->diff->pfx.ptr) > 0) { + return file_delta_new__from_one( + info->diff, GIT_STATUS_DELETED, git_tree_entry_attributes(tree_entry), + git_tree_entry_id(tree_entry), info->diff->pfx.ptr); + } + + /* create modified delta for non-matching tree & index entries */ + info->index_pos++; + + if (git_oid_cmp(&idx_entry->oid, git_tree_entry_id(tree_entry)) || + idx_entry->mode != git_tree_entry_attributes(tree_entry)) + { + git_tree_diff_data tdiff; + tdiff.old_attr = git_tree_entry_attributes(tree_entry); + tdiff.new_attr = idx_entry->mode; + tdiff.status = GIT_STATUS_MODIFIED; + tdiff.path = idx_entry->path; + git_oid_cpy(&tdiff.old_oid, git_tree_entry_id(tree_entry)); + git_oid_cpy(&tdiff.new_oid, &idx_entry->oid); + + error = file_delta_new__from_tree_diff(info->diff, &tdiff); + } + + return error; + +} + +int git_diff_index_to_tree( + git_repository *repo, + const git_diff_options *opts, + git_tree *old, + git_diff_list **diff_ptr) +{ + int error; + index_to_tree_info info = {0}; + + if ((info.diff = git_diff_list_alloc(repo, opts)) == NULL) + return GIT_ENOMEM; + + if ((error = git_repository_index(&info.index, repo)) == GIT_SUCCESS) { + error = git_tree_walk( + old, diff_index_to_tree_cb, GIT_TREEWALK_POST, &info); + if (error == GIT_SUCCESS) + error = add_new_index_deltas(&info, NULL); + git_index_free(info.index); + } + git_buf_free(&info.diff->pfx); + + if (error != GIT_SUCCESS) + git_diff_list_free(info.diff); + else + *diff_ptr = info.diff; + + return error; +} + typedef struct { git_diff_list *diff; void *cb_data; @@ -331,6 +486,26 @@ static int set_file_is_binary( return GIT_SUCCESS; } +static void setup_xdiff_config(git_diff_options *opts, xdemitconf_t *cfg) +{ + memset(cfg, 0, sizeof(xdemitconf_t)); + + cfg->ctxlen = + (!opts || !opts->context_lines) ? 3 : opts->context_lines; + cfg->interhunkctxlen = + (!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines; + + if (!opts) + return; + + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE) + cfg->flags |= XDF_WHITESPACE_FLAGS; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) + cfg->flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) + cfg->flags |= XDF_IGNORE_WHITESPACE_AT_EOL; +} + int git_diff_foreach( git_diff_list *diff, void *data, @@ -341,17 +516,23 @@ int git_diff_foreach( int error = GIT_SUCCESS; diff_info di; git_diff_delta *delta; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; di.diff = diff; di.cb_data = data; di.hunk_cb = hunk_cb; di.line_cb = line_cb; + memset(&xdiff_params, 0, sizeof(xdiff_params)); + setup_xdiff_config(&diff->opts, &xdiff_config); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &di; + git_vector_foreach(&diff->files, di.index, delta) { - mmfile_t old, new; - xpparam_t params; - xdemitconf_t cfg; - xdemitcb_t callback; + mmfile_t old_data, new_data; /* map files */ if (hunk_cb || line_cb) { @@ -365,12 +546,12 @@ int git_diff_foreach( { error = git_blob_lookup( &delta->old_blob, diff->repo, &delta->old_oid); - old.ptr = (char *)git_blob_rawcontent(delta->old_blob); - old.size = git_blob_rawsize(delta->old_blob); + old_data.ptr = (char *)git_blob_rawcontent(delta->old_blob); + old_data.size = git_blob_rawsize(delta->old_blob); } else { delta->old_blob = NULL; - old.ptr = ""; - old.size = 0; + old_data.ptr = ""; + old_data.size = 0; } if (delta->status == GIT_STATUS_ADDED || @@ -378,21 +559,25 @@ int git_diff_foreach( { error = git_blob_lookup( &delta->new_blob, diff->repo, &delta->new_oid); - new.ptr = (char *)git_blob_rawcontent(delta->new_blob); - new.size = git_blob_rawsize(delta->new_blob); + new_data.ptr = (char *)git_blob_rawcontent(delta->new_blob); + new_data.size = git_blob_rawsize(delta->new_blob); } else { delta->new_blob = NULL; - new.ptr = ""; - new.size = 0; + new_data.ptr = ""; + new_data.size = 0; } } - if (diff->opts.force_text) + if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) delta->binary = 0; else if ((error = set_file_is_binary( - diff->repo, delta, &old, &new)) < GIT_SUCCESS) + diff->repo, delta, &old_data, &new_data)) < GIT_SUCCESS) break; + /* TODO: if ignore_whitespace is set, then we *must* do text + * diffs to tell if a file has really been changed. + */ + if (file_cb != NULL) { error = file_cb(data, delta, (float)di.index / diff->files.length); if (error != GIT_SUCCESS) @@ -411,19 +596,8 @@ int git_diff_foreach( di.delta = delta; - memset(¶ms, 0, sizeof(params)); - - memset(&cfg, 0, sizeof(cfg)); - cfg.ctxlen = diff->opts.context_lines || 3; - cfg.interhunkctxlen = diff->opts.interhunk_lines || 3; - if (diff->opts.ignore_whitespace) - cfg.flags |= XDF_WHITESPACE_FLAGS; - - memset(&callback, 0, sizeof(callback)); - callback.outf = diff_output_cb; - callback.priv = &di; - - xdl_diff(&old, &new, ¶ms, &cfg, &callback); + xdl_diff(&old_data, &new_data, + &xdiff_params, &xdiff_config, &xdiff_callback); git_blob_free(delta->old_blob); delta->old_blob = NULL; @@ -436,6 +610,7 @@ int git_diff_foreach( } typedef struct { + git_diff_list *diff; git_diff_output_fn print_cb; void *cb_data; git_buf *buf; @@ -483,7 +658,8 @@ static int print_compact(void *data, git_diff_delta *delta, float progress) if (delta->new_path != NULL) git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, delta->path, old_suffix, delta->new_path, new_suffix); - else if (delta->old_attr != delta->new_attr) + else if (delta->old_attr != delta->new_attr && + delta->old_attr != 0 && delta->new_attr != 0) git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code, delta->path, new_suffix, delta->old_attr, delta->new_attr); else if (old_suffix != ' ') @@ -506,6 +682,7 @@ int git_diff_print_compact( git_buf buf = GIT_BUF_INIT; print_info pi; + pi.diff = diff; pi.print_cb = print_cb; pi.cb_data = cb_data; pi.buf = &buf; @@ -549,15 +726,15 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) { int error; print_info *pi = data; - const char *oldpfx = "a/"; + const char *oldpfx = pi->diff->opts.src_prefix; const char *oldpath = delta->path; - const char *newpfx = "b/"; + const char *newpfx = pi->diff->opts.dst_prefix; const char *newpath = delta->new_path ? delta->new_path : delta->path; GIT_UNUSED_ARG(progress); git_buf_clear(pi->buf); - git_buf_printf(pi->buf, "diff --git a/%s b/%s\n", delta->path, newpath); + git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->path, newpfx, newpath); if ((error = print_oid_range(pi, delta)) < GIT_SUCCESS) return error; @@ -647,6 +824,7 @@ int git_diff_print_patch( git_buf buf = GIT_BUF_INIT; print_info pi; + pi.diff = diff; pi.print_cb = print_cb; pi.cb_data = cb_data; pi.buf = &buf; @@ -671,11 +849,17 @@ int git_diff_blobs( diff_info di; git_diff_delta delta; mmfile_t old, new; - xpparam_t params; - xdemitconf_t cfg; - xdemitcb_t callback; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; - assert(repo && options); + assert(repo); + + if (options && (options->flags & GIT_DIFF_REVERSE)) { + git_blob *swap = old_blob; + old_blob = new_blob; + new_blob = swap; + } if (old_blob) { old.ptr = (char *)git_blob_rawcontent(old_blob); @@ -712,21 +896,15 @@ int git_diff_blobs( di.delta = δ di.cb_data = cb_data; di.hunk_cb = hunk_cb; - di.line_cb = line_cb; + di.line_cb = line_cb; - memset(¶ms, 0, sizeof(params)); + memset(&xdiff_params, 0, sizeof(xdiff_params)); + setup_xdiff_config(options, &xdiff_config); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &di; - memset(&cfg, 0, sizeof(cfg)); - cfg.ctxlen = options->context_lines || 3; - cfg.interhunkctxlen = options->interhunk_lines || 3; - if (options->ignore_whitespace) - cfg.flags |= XDF_WHITESPACE_FLAGS; - - memset(&callback, 0, sizeof(callback)); - callback.outf = diff_output_cb; - callback.priv = &di; - - xdl_diff(&old, &new, ¶ms, &cfg, &callback); + xdl_diff(&old, &new, &xdiff_params, &xdiff_config, &xdiff_callback); return GIT_SUCCESS; } diff --git a/src/diff.h b/src/diff.h index 1bb5c36f0..e7764a8eb 100644 --- a/src/diff.h +++ b/src/diff.h @@ -14,8 +14,11 @@ struct git_diff_list { git_repository *repo; git_diff_options opts; - git_buf pfx; git_vector files; /* vector of git_diff_file_delta */ + + /* the following are just used while processing the diff list */ + git_buf pfx; + git_status_t mode; }; #endif diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c index 048b05c79..7aa8ceb22 100644 --- a/tests-clar/diff/blob.c +++ b/tests-clar/diff/blob.c @@ -22,7 +22,7 @@ void test_diff_blob__0(void) { git_blob *a, *b, *c, *d; git_oid a_oid, b_oid, c_oid, d_oid; - git_diff_options opts; + git_diff_options opts = {0}; diff_expects exp; /* tests/resources/attr/root_test1 */ @@ -44,8 +44,7 @@ void test_diff_blob__0(void) /* Doing the equivalent of a `git diff -U1` on these files */ opts.context_lines = 1; - opts.interhunk_lines = 0; - opts.ignore_whitespace = 0; + opts.interhunk_lines = 1; memset(&exp, 0, sizeof(exp)); cl_git_pass(git_diff_blobs( diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index 3fcf45c10..b32c4bc2d 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -82,3 +82,23 @@ int diff_line_fn( } return 0; } + +git_tree *resolve_commit_oid_to_tree( + git_repository *repo, + const char *partial_oid) +{ + size_t len = strlen(partial_oid); + git_oid oid; + git_object *obj; + git_tree *tree; + + if (git_oid_fromstrn(&oid, partial_oid, len) == 0) + git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY); + cl_assert(obj); + if (git_object_type(obj) == GIT_OBJ_TREE) + return (git_tree *)obj; + cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT); + cl_git_pass(git_commit_tree(&tree, (git_commit *)obj)); + git_object_free(obj); + return tree; +} diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h index 4c3e7580e..035c000f5 100644 --- a/tests-clar/diff/diff_helpers.h +++ b/tests-clar/diff/diff_helpers.h @@ -38,3 +38,4 @@ extern int diff_line_fn( char line_origin, const char *content, size_t content_len); + diff --git a/tests-clar/diff/index.c b/tests-clar/diff/index.c new file mode 100644 index 000000000..0941c7c21 --- /dev/null +++ b/tests-clar/diff/index.c @@ -0,0 +1,96 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_index__initialize(void) +{ + cl_fixture_sandbox("status"); + cl_git_pass(p_rename("status/.gitted", "status/.git")); + cl_git_pass(git_repository_open(&g_repo, "status/.git")); +} + +void test_diff_index__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; + cl_fixture_cleanup("status"); +} + +void test_diff_index__0(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + cl_assert(a); + cl_assert(b); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 26a125ee1bf + * - git diff -U1 --cached 26a125ee1bf + * - mv .git .gitted + */ + cl_assert(exp.files == 8); + cl_assert(exp.file_adds == 3); + cl_assert(exp.file_dels == 2); + cl_assert(exp.file_mods == 3); + + cl_assert(exp.hunks == 8); + + cl_assert(exp.lines == 11); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 6); + cl_assert(exp.line_dels == 2); + + git_diff_list_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff)); + + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 0017bd4ab1ec3 + * - git diff -U1 --cached 0017bd4ab1ec3 + * - mv .git .gitted + */ + cl_assert(exp.files == 12); + cl_assert(exp.file_adds == 7); + cl_assert(exp.file_dels == 2); + cl_assert(exp.file_mods == 3); + + cl_assert(exp.hunks == 12); + + cl_assert(exp.lines == 16); + cl_assert(exp.line_ctxt == 3); + cl_assert(exp.line_adds == 11); + cl_assert(exp.line_dels == 2); + + git_diff_list_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c index 3c5d2a9f2..836db5765 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -18,34 +18,16 @@ void test_diff_tree__cleanup(void) cl_fixture_cleanup("attr"); } -static git_tree *resolve_commit_oid_to_tree(const char *partial_oid) -{ - size_t len = strlen(partial_oid); - git_oid oid; - git_object *obj; - git_tree *tree; - - if (git_oid_fromstrn(&oid, partial_oid, len) == 0) - git_object_lookup_prefix(&obj, g_repo, &oid, len, GIT_OBJ_ANY); - cl_assert(obj); - if (git_object_type(obj) == GIT_OBJ_TREE) - return (git_tree *)obj; - cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT); - cl_git_pass(git_commit_tree(&tree, (git_commit *)obj)); - git_object_free(obj); - return tree; -} - void test_diff_tree__0(void) { /* grabbed a couple of commit oids from the history of the attr repo */ const char *a_commit = "605812a"; const char *b_commit = "370fe9ec22"; const char *c_commit = "f5b0af1fb4f5c"; - git_tree *a = resolve_commit_oid_to_tree(a_commit); - git_tree *b = resolve_commit_oid_to_tree(b_commit); - git_tree *c = resolve_commit_oid_to_tree(c_commit); - git_diff_options opts; + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_tree *c = resolve_commit_oid_to_tree(g_repo, c_commit); + git_diff_options opts = {0}; git_diff_list *diff = NULL; diff_expects exp; @@ -53,8 +35,7 @@ void test_diff_tree__0(void) cl_assert(b); opts.context_lines = 1; - opts.interhunk_lines = 0; - opts.ignore_whitespace = 0; + opts.interhunk_lines = 1; memset(&exp, 0, sizeof(exp));