summaryrefslogtreecommitdiff
path: root/src/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/diff.c')
-rw-r--r--src/diff.c341
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;
}