diff options
Diffstat (limited to 'src/diff.c')
-rw-r--r-- | src/diff.c | 341 |
1 files changed, 130 insertions, 211 deletions
diff --git a/src/diff.c b/src/diff.c index 55f6ee7d5..6f48d72a2 100644 --- a/src/diff.c +++ b/src/diff.c @@ -10,76 +10,7 @@ #include "config.h" #include "attr_file.h" #include "filter.h" - -static char *diff_prefix_from_pathspec(const git_strarray *pathspec) -{ - git_buf prefix = GIT_BUF_INIT; - const char *scan; - - if (git_buf_common_prefix(&prefix, pathspec) < 0) - return NULL; - - /* diff prefix will only be leading non-wildcards */ - for (scan = prefix.ptr; *scan; ++scan) { - if (git__iswildcard(*scan) && - (scan == prefix.ptr || (*(scan - 1) != '\\'))) - break; - } - git_buf_truncate(&prefix, scan - prefix.ptr); - - if (prefix.size <= 0) { - git_buf_free(&prefix); - return NULL; - } - - git_buf_unescape(&prefix); - - return git_buf_detach(&prefix); -} - -static bool diff_pathspec_is_interesting(const git_strarray *pathspec) -{ - const char *str; - - if (pathspec == NULL || pathspec->count == 0) - return false; - if (pathspec->count > 1) - return true; - - str = pathspec->strings[0]; - if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.'))) - return false; - return true; -} - -static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) -{ - unsigned int i; - git_attr_fnmatch *match; - - if (!diff->pathspec.length) - return true; - - git_vector_foreach(&diff->pathspec, i, match) { - int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0; - - if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) && - result == FNM_NOMATCH) - result = p_fnmatch(match->pattern, path, 0); - - /* if we didn't match, look for exact dirname prefix match */ - if (result == FNM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && - strncmp(path, match->pattern, match->length) == 0 && - path[match->length] == '/') - result = 0; - - if (result == 0) - return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; - } - - return false; -} +#include "pathspec.h" static git_diff_delta *diff_delta__alloc( git_diff_list *diff, @@ -125,7 +56,10 @@ static int diff_delta__from_one( (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) return 0; - if (!diff_path_matches_pathspec(diff, entry->path)) + if (!git_pathspec_match_path( + &diff->pathspec, entry->path, + (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, + (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -295,7 +229,6 @@ static git_diff_list *git_diff_list_alloc( git_repository *repo, const git_diff_options *opts) { git_config *cfg; - size_t i; git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); if (diff == NULL) return NULL; @@ -333,7 +266,10 @@ static git_diff_list *git_diff_list_alloc( return diff; memcpy(&diff->opts, opts, sizeof(git_diff_options)); - memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec)); + + /* pathspec init will do nothing for empty pathspec */ + if (git_pathspec_init(&diff->pathspec, &opts->pathspec, &diff->pool) < 0) + goto fail; /* TODO: handle config diff.mnemonicprefix, diff.noprefix */ @@ -355,35 +291,6 @@ static git_diff_list *git_diff_list_alloc( if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; - /* only copy pathspec if it is "interesting" so we can test - * diff->pathspec.length > 0 to know if it is worth calling - * fnmatch as we iterate. - */ - if (!diff_pathspec_is_interesting(&opts->pathspec)) - return diff; - - if (git_vector_init( - &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0) - goto fail; - - for (i = 0; i < opts->pathspec.count; ++i) { - int ret; - const char *pattern = opts->pathspec.strings[i]; - git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); - if (!match) - goto fail; - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; - ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern); - if (ret == GIT_ENOTFOUND) { - git__free(match); - continue; - } else if (ret < 0) - goto fail; - - if (git_vector_insert(&diff->pathspec, match) < 0) - goto fail; - } - return diff; fail: @@ -394,7 +301,6 @@ fail: static void diff_list_free(git_diff_list *diff) { git_diff_delta *delta; - git_attr_fnmatch *match; unsigned int i; git_vector_foreach(&diff->deltas, i, delta) { @@ -403,12 +309,7 @@ static void diff_list_free(git_diff_list *diff) } git_vector_free(&diff->deltas); - git_vector_foreach(&diff->pathspec, i, match) { - git__free(match); - diff->pathspec.contents[i] = NULL; - } - git_vector_free(&diff->pathspec); - + git_pathspec_free(&diff->pathspec); git_pool_clear(&diff->pool); git__free(diff); } @@ -426,24 +327,39 @@ void git_diff_list_addref(git_diff_list *diff) GIT_REFCOUNT_INC(diff); } -static int oid_for_workdir_item( +int git_diff__oid_for_file( git_repository *repo, - const git_index_entry *item, + const char *path, + uint16_t mode, + git_off_t size, git_oid *oid) { int result = 0; git_buf full_path = GIT_BUF_INIT; if (git_buf_joinpath( - &full_path, git_repository_workdir(repo), item->path) < 0) + &full_path, git_repository_workdir(repo), path) < 0) return -1; + if (!mode) { + struct stat st; + + if (p_stat(path, &st) < 0) { + giterr_set(GITERR_OS, "Could not stat '%s'", path); + result = -1; + goto cleanup; + } + + mode = st.st_mode; + size = st.st_size; + } + /* calculate OID for file if possible */ - if (S_ISGITLINK(item->mode)) { + if (S_ISGITLINK(mode)) { git_submodule *sm; const git_oid *sm_oid; - if (!git_submodule_lookup(&sm, repo, item->path) && + if (!git_submodule_lookup(&sm, repo, path) && (sm_oid = git_submodule_wd_oid(sm)) != NULL) git_oid_cpy(oid, sm_oid); else { @@ -453,23 +369,22 @@ static int oid_for_workdir_item( giterr_clear(); memset(oid, 0, sizeof(*oid)); } - } else if (S_ISLNK(item->mode)) + } else if (S_ISLNK(mode)) { result = git_odb__hashlink(oid, full_path.ptr); - else if (!git__is_sizet(item->file_size)) { - giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + } else if (!git__is_sizet(size)) { + giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path); result = -1; } else { git_vector filters = GIT_VECTOR_INIT; - result = git_filters_load( - &filters, repo, item->path, GIT_FILTER_TO_ODB); + result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB); if (result >= 0) { int fd = git_futils_open_ro(full_path.ptr); if (fd < 0) result = fd; else { result = git_odb__hashfd_filtered( - oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB, &filters); + oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters); p_close(fd); } } @@ -477,8 +392,8 @@ static int oid_for_workdir_item( git_filters_free(&filters); } +cleanup: git_buf_free(&full_path); - return result; } @@ -499,7 +414,10 @@ static int maybe_modified( GIT_UNUSED(old_iter); - if (!diff_path_matches_pathspec(diff, oitem->path)) + if (!git_pathspec_match_path( + &diff->pathspec, oitem->path, + (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, + (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)) return 0; /* on platforms with no symlinks, preserve mode of existing symlinks */ @@ -535,8 +453,7 @@ static int maybe_modified( } /* if oids and modes match, then file is unmodified */ - else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && - omode == nmode) + else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode) status = GIT_DELTA_UNMODIFIED; /* if we have an unknown OID and a workdir iterator, then check some @@ -590,44 +507,28 @@ static int maybe_modified( * haven't calculated the OID of the new item, then calculate it now */ if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) { - if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) - return -1; - else if (omode == nmode && git_oid_equal(&oitem->oid, &noid)) + if (!use_noid) { + if (git_diff__oid_for_file(diff->repo, + nitem->path, nitem->mode, nitem->file_size, &noid) < 0) + return -1; + use_noid = &noid; + } + if (omode == nmode && git_oid_equal(&oitem->oid, use_noid)) status = GIT_DELTA_UNMODIFIED; - - /* store calculated oid so we don't have to recalc later */ - use_noid = &noid; } return diff_delta__from_two( diff, status, oitem, omode, nitem, nmode, use_noid); } -static int git_index_entry_cmp_case(const void *a, const void *b) -{ - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - return strcmp(entry_a->path, entry_b->path); -} - -static int git_index_entry_cmp_icase(const void *a, const void *b) -{ - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - return strcasecmp(entry_a->path, entry_b->path); -} - static bool entry_is_prefixed( + git_diff_list *diff, const git_index_entry *item, - git_iterator *prefix_iterator, const git_index_entry *prefix_item) { size_t pathlen; - if (!prefix_item || - ITERATOR_PREFIXCMP(*prefix_iterator, prefix_item->path, item->path)) + if (!prefix_item || diff->pfxcomp(prefix_item->path, item->path)) return false; pathlen = strlen(item->path); @@ -637,6 +538,35 @@ static bool entry_is_prefixed( prefix_item->path[pathlen] == '/'); } +static int diff_list_init_from_iterators( + git_diff_list *diff, + git_iterator *old_iter, + git_iterator *new_iter) +{ + diff->old_src = old_iter->type; + diff->new_src = new_iter->type; + + /* Use case-insensitive compare if either iterator has + * the ignore_case bit set */ + if (!old_iter->ignore_case && !new_iter->ignore_case) { + diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE; + + diff->strcomp = strcmp; + diff->strncomp = strncmp; + diff->pfxcomp = git__prefixcmp; + diff->entrycomp = git_index_entry__cmp; + } else { + diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + + diff->strcomp = strcasecmp; + diff->strncomp = strncasecmp; + diff->pfxcomp = git__prefixcmp_icase; + diff->entrycomp = git_index_entry__cmp_icase; + } + + return 0; +} + static int diff_from_iterators( git_repository *repo, const git_diff_options *opts, /**< can be NULL for defaults */ @@ -644,37 +574,31 @@ static int diff_from_iterators( git_iterator *new_iter, git_diff_list **diff_ptr) { + int error = 0; const git_index_entry *oitem, *nitem; git_buf ignore_prefix = GIT_BUF_INIT; git_diff_list *diff = git_diff_list_alloc(repo, opts); - git_vector_cmp entry_compare; - if (!diff) - goto fail; - - diff->old_src = old_iter->type; - diff->new_src = new_iter->type; + *diff_ptr = NULL; - /* Use case-insensitive compare if either iterator has - * the ignore_case bit set */ - if (!old_iter->ignore_case && !new_iter->ignore_case) { - entry_compare = git_index_entry_cmp_case; - diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE; - } else { - entry_compare = git_index_entry_cmp_icase; - diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + if (!diff || + diff_list_init_from_iterators(diff, old_iter, new_iter) < 0) + goto fail; + if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { /* If one of the iterators doesn't have ignore_case set, * then that's unfortunate because we'll have to spool * its data, sort it icase, and then use that for our * merge join to the other iterator that is icase sorted */ - if (!old_iter->ignore_case) { - if (git_iterator_spoolandsort(&old_iter, old_iter, git_index_entry_cmp_icase, true) < 0) - goto fail; - } else if (!new_iter->ignore_case) { - if (git_iterator_spoolandsort(&new_iter, new_iter, git_index_entry_cmp_icase, true) < 0) - goto fail; - } + if (!old_iter->ignore_case && + git_iterator_spoolandsort( + &old_iter, old_iter, diff->entrycomp, true) < 0) + goto fail; + + if (!new_iter->ignore_case && + git_iterator_spoolandsort( + &new_iter, new_iter, diff->entrycomp, true) < 0) + goto fail; } if (git_iterator_current(old_iter, &oitem) < 0 || @@ -685,7 +609,7 @@ static int diff_from_iterators( while (oitem || nitem) { /* create DELETED records for old items not matched in new */ - if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) { + if (oitem && (!nitem || diff->entrycomp(oitem, nitem) < 0)) { if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0) goto fail; @@ -693,7 +617,7 @@ static int diff_from_iterators( * instead of just generating a DELETE record */ if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 && - entry_is_prefixed(oitem, new_iter, nitem)) + entry_is_prefixed(diff, oitem, nitem)) { /* this entry has become a tree! convert to TYPECHANGE */ git_diff_delta *last = diff_delta__last_for_item(diff, oitem); @@ -710,13 +634,12 @@ static int diff_from_iterators( /* create ADDED, TRACKED, or IGNORED records for new items not * matched in old (and/or descend into directories as needed) */ - else if (nitem && (!oitem || entry_compare(oitem, nitem) > 0)) { + else if (nitem && (!oitem || diff->entrycomp(oitem, nitem) > 0)) { git_delta_t delta_type = GIT_DELTA_UNTRACKED; /* check if contained in ignored parent directory */ if (git_buf_len(&ignore_prefix) && - ITERATOR_PREFIXCMP(*old_iter, nitem->path, - git_buf_cstr(&ignore_prefix)) == 0) + diff->pfxcomp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0) delta_type = GIT_DELTA_IGNORED; if (S_ISDIR(nitem->mode)) { @@ -725,7 +648,7 @@ static int diff_from_iterators( * directories and it is not under an ignored directory. */ bool contains_tracked = - entry_is_prefixed(nitem, old_iter, oitem); + entry_is_prefixed(diff, nitem, oitem); bool recurse_untracked = (delta_type == GIT_DELTA_UNTRACKED && (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0); @@ -789,7 +712,7 @@ static int diff_from_iterators( */ if (delta_type != GIT_DELTA_IGNORED && (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 && - entry_is_prefixed(nitem, old_iter, oitem)) + entry_is_prefixed(diff, nitem, oitem)) { /* this entry was a tree! convert to TYPECHANGE */ git_diff_delta *last = diff_delta__last_for_item(diff, oitem); @@ -807,7 +730,7 @@ static int diff_from_iterators( * (or ADDED and DELETED pair if type changed) */ else { - assert(oitem && nitem && entry_compare(oitem, nitem) == 0); + assert(oitem && nitem && diff->entrycomp(oitem, nitem) == 0); if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 || git_iterator_advance(old_iter, &oitem) < 0 || @@ -816,21 +739,19 @@ static int diff_from_iterators( } } - git_iterator_free(old_iter); - git_iterator_free(new_iter); - git_buf_free(&ignore_prefix); - *diff_ptr = diff; - return 0; fail: + if (!*diff_ptr) { + git_diff_list_free(diff); + error = -1; + } + git_iterator_free(old_iter); git_iterator_free(new_iter); git_buf_free(&ignore_prefix); - git_diff_list_free(diff); - *diff_ptr = NULL; - return -1; + return error; } @@ -842,15 +763,15 @@ int git_diff_tree_to_tree( git_diff_list **diff) { git_iterator *a = NULL, *b = NULL; - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; assert(repo && old_tree && new_tree && diff); - if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || - git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0) + if (git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx) < 0 || + git_iterator_for_tree_range(&b, repo, new_tree, pfx, pfx) < 0) return -1; - git__free(prefix); + git__free(pfx); return diff_from_iterators(repo, opts, a, b, diff); } @@ -862,20 +783,20 @@ int git_diff_index_to_tree( git_diff_list **diff) { git_iterator *a = NULL, *b = NULL; - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; assert(repo && diff); - if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || - git_iterator_for_index_range(&b, repo, prefix, prefix) < 0) + if (git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx) < 0 || + git_iterator_for_index_range(&b, repo, pfx, pfx) < 0) goto on_error; - git__free(prefix); + git__free(pfx); return diff_from_iterators(repo, opts, a, b, diff); on_error: - git__free(prefix); + git__free(pfx); git_iterator_free(a); return -1; } @@ -885,23 +806,22 @@ int git_diff_workdir_to_index( const git_diff_options *opts, git_diff_list **diff) { - git_iterator *a = NULL, *b = NULL; int error; - - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + git_iterator *a = NULL, *b = NULL; + char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; assert(repo && diff); - if ((error = git_iterator_for_index_range(&a, repo, prefix, prefix)) < 0 || - (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0) + if ((error = git_iterator_for_index_range(&a, repo, pfx, pfx)) < 0 || + (error = git_iterator_for_workdir_range(&b, repo, pfx, pfx)) < 0) goto on_error; - git__free(prefix); + git__free(pfx); return diff_from_iterators(repo, opts, a, b, diff); on_error: - git__free(prefix); + git__free(pfx); git_iterator_free(a); return error; } @@ -910,26 +830,25 @@ on_error: int git_diff_workdir_to_tree( git_repository *repo, const git_diff_options *opts, - git_tree *old_tree, + git_tree *tree, git_diff_list **diff) { - git_iterator *a = NULL, *b = NULL; int error; + git_iterator *a = NULL, *b = NULL; + char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; - - assert(repo && old_tree && diff); + assert(repo && tree && diff); - if ((error = git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix)) < 0 || - (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0) + if ((error = git_iterator_for_tree_range(&a, repo, tree, pfx, pfx)) < 0 || + (error = git_iterator_for_workdir_range(&b, repo, pfx, pfx)) < 0) goto on_error; - git__free(prefix); + git__free(pfx); return diff_from_iterators(repo, opts, a, b, diff); on_error: - git__free(prefix); + git__free(pfx); git_iterator_free(a); return error; } |