Commit e77e53ed by Vicent Martí

Merge pull request #627 from arrbee/diff-with-pathspec

Diff with pathspec
parents d1f33156 14a513e0
......@@ -87,6 +87,7 @@ typedef struct {
} git_strarray;
GIT_EXTERN(void) git_strarray_free(git_strarray *array);
GIT_EXTERN(int) git_strarray_copy(git_strarray *tgt, const git_strarray *src);
/**
* Return the version of the libgit2 library
......
......@@ -334,6 +334,10 @@ int git_attr_fnmatch__parse(
spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
slash_count++;
}
/* remember if we see an unescaped wildcard in pattern */
else if ((*scan == '*' || *scan == '.' || *scan == '[') &&
(scan == pattern || (*(scan - 1) != '\\')))
spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
}
*base = scan;
......
......@@ -20,6 +20,7 @@
#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2)
#define GIT_ATTR_FNMATCH_MACRO (1U << 3)
#define GIT_ATTR_FNMATCH_IGNORE (1U << 4)
#define GIT_ATTR_FNMATCH_HASWILD (1U << 5)
typedef struct {
char *pattern;
......
......@@ -9,6 +9,50 @@
#include "diff.h"
#include "fileops.h"
#include "config.h"
#include "attr_file.h"
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 = git__fnmatch(match->pattern, path, 0);
/* if we didn't match, look for exact dirname prefix match */
if (result == GIT_ENOMATCH &&
(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;
if (result != GIT_ENOMATCH)
giterr_clear();
}
return false;
}
static void diff_delta__free(git_diff_delta *delta)
{
......@@ -143,6 +187,9 @@ static int diff_delta__from_one(
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return 0;
if (!diff_path_matches_pathspec(diff, entry->path))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
GITERR_CHECK_ALLOC(delta);
......@@ -246,6 +293,7 @@ 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;
......@@ -269,6 +317,7 @@ 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));
diff->opts.src_prefix = diff_strdup_prefix(
opts->src_prefix ? opts->src_prefix : DIFF_SRC_PREFIX_DEFAULT);
......@@ -287,21 +336,45 @@ static git_diff_list *git_diff_list_alloc(
if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0)
goto fail;
/* TODO: do something safe with the pathspec strarray */
/* 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, 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;
ret = git_attr_fnmatch__parse(match, 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:
git_vector_free(&diff->deltas);
git__free(diff->opts.src_prefix);
git__free(diff->opts.dst_prefix);
git__free(diff);
git_diff_list_free(diff);
return NULL;
}
void git_diff_list_free(git_diff_list *diff)
{
git_diff_delta *delta;
git_attr_fnmatch *match;
unsigned int i;
if (!diff)
......@@ -312,6 +385,17 @@ void git_diff_list_free(git_diff_list *diff)
diff->deltas.contents[i] = NULL;
}
git_vector_free(&diff->deltas);
git_vector_foreach(&diff->pathspec, i, match) {
if (match != NULL) {
git__free(match->pattern);
match->pattern = NULL;
git__free(match);
diff->pathspec.contents[i] = NULL;
}
}
git_vector_free(&diff->pathspec);
git__free(diff->opts.src_prefix);
git__free(diff->opts.dst_prefix);
git__free(diff);
......@@ -366,6 +450,9 @@ static int maybe_modified(
GIT_UNUSED(old);
if (!diff_path_matches_pathspec(diff, oitem->path))
return 0;
/* on platforms with no symlinks, promote plain files to symlinks */
if (S_ISLNK(omode) && S_ISREG(nmode) &&
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
......
......@@ -24,6 +24,7 @@ enum {
struct git_diff_list {
git_repository *repo;
git_diff_options opts;
git_vector pathspec;
git_vector deltas; /* vector of git_diff_file_delta */
git_iterator_type_t old_src;
git_iterator_type_t new_src;
......
......@@ -205,6 +205,7 @@ int git_status_foreach(
{
git_status_options opts;
memset(&opts, 0, sizeof(opts));
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
GIT_STATUS_OPT_INCLUDE_UNTRACKED |
......
......@@ -31,6 +31,35 @@ void git_strarray_free(git_strarray *array)
git__free(array->strings);
}
int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
{
size_t i;
assert(tgt && src);
memset(tgt, 0, sizeof(*tgt));
if (!src->count)
return 0;
tgt->strings = git__calloc(src->count, sizeof(char *));
GITERR_CHECK_ALLOC(tgt->strings);
for (i = 0; i < src->count; ++i) {
tgt->strings[tgt->count] = git__strdup(src->strings[i]);
if (!tgt->strings[tgt->count]) {
git_strarray_free(tgt);
memset(tgt, 0, sizeof(*tgt));
return -1;
}
tgt->count++;
}
return 0;
}
int git__fnmatch(const char *pattern, const char *name, int flags)
{
int ret;
......
......@@ -20,7 +20,7 @@ void test_attr_file__simple_read(void)
cl_assert(rule != NULL);
cl_assert_strequal("*", rule->match.pattern);
cl_assert(rule->match.length == 1);
cl_assert(rule->match.flags == 0);
cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
cl_assert(rule->assigns.length == 1);
assign = get_assign(rule, 0);
......@@ -74,14 +74,16 @@ void test_attr_file__match_variants(void)
rule = get_rule(4);
cl_assert_strequal("pat4.*", rule->match.pattern);
cl_assert(rule->match.flags == 0);
cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
rule = get_rule(5);
cl_assert_strequal("*.pat5", rule->match.pattern);
cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
rule = get_rule(7);
cl_assert_strequal("pat7[a-e]??[xyz]", rule->match.pattern);
cl_assert(rule->assigns.length == 1);
cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
assign = get_assign(rule,0);
cl_assert_strequal("attr7", assign->name);
cl_assert(GIT_ATTR_TRUE(assign->value));
......
......@@ -164,6 +164,79 @@ void test_diff_workdir__to_tree(void)
git_tree_free(b);
}
void test_diff_workdir__to_index_with_pathspec(void)
{
git_diff_options opts = {0};
git_diff_list *diff = NULL;
diff_expects exp;
char *pathspec = NULL;
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
opts.pathspec.strings = &pathspec;
opts.pathspec.count = 1;
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(12, exp.files);
cl_assert_equal_i(0, exp.file_adds);
cl_assert_equal_i(4, exp.file_dels);
cl_assert_equal_i(4, exp.file_mods);
cl_assert_equal_i(1, exp.file_ignored);
cl_assert_equal_i(3, exp.file_untracked);
git_diff_list_free(diff);
memset(&exp, 0, sizeof(exp));
pathspec = "modified_file";
cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(1, exp.files);
cl_assert_equal_i(0, exp.file_adds);
cl_assert_equal_i(0, exp.file_dels);
cl_assert_equal_i(1, exp.file_mods);
cl_assert_equal_i(0, exp.file_ignored);
cl_assert_equal_i(0, exp.file_untracked);
git_diff_list_free(diff);
memset(&exp, 0, sizeof(exp));
pathspec = "subdir";
cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(3, exp.files);
cl_assert_equal_i(0, exp.file_adds);
cl_assert_equal_i(1, exp.file_dels);
cl_assert_equal_i(1, exp.file_mods);
cl_assert_equal_i(0, exp.file_ignored);
cl_assert_equal_i(1, exp.file_untracked);
git_diff_list_free(diff);
memset(&exp, 0, sizeof(exp));
pathspec = "*_deleted";
cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(2, exp.files);
cl_assert_equal_i(0, exp.file_adds);
cl_assert_equal_i(2, exp.file_dels);
cl_assert_equal_i(0, exp.file_mods);
cl_assert_equal_i(0, exp.file_ignored);
cl_assert_equal_i(0, exp.file_untracked);
git_diff_list_free(diff);
}
/* PREPARATION OF TEST DATA
*
* Since there is no command line equivalent of git_diff_workdir_to_tree,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment