Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diff with pathspec #627

Merged
merged 1 commit into from
Apr 14, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/git2/common.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ typedef struct {
} git_strarray; } git_strarray;


GIT_EXTERN(void) git_strarray_free(git_strarray *array); 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 * Return the version of the libgit2 library
Expand Down
4 changes: 4 additions & 0 deletions src/attr_file.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ int git_attr_fnmatch__parse(
spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
slash_count++; 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; *base = scan;
Expand Down
1 change: 1 addition & 0 deletions src/attr_file.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) #define GIT_ATTR_FNMATCH_FULLPATH (1U << 2)
#define GIT_ATTR_FNMATCH_MACRO (1U << 3) #define GIT_ATTR_FNMATCH_MACRO (1U << 3)
#define GIT_ATTR_FNMATCH_IGNORE (1U << 4) #define GIT_ATTR_FNMATCH_IGNORE (1U << 4)
#define GIT_ATTR_FNMATCH_HASWILD (1U << 5)


typedef struct { typedef struct {
char *pattern; char *pattern;
Expand Down
97 changes: 92 additions & 5 deletions src/diff.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -9,6 +9,50 @@
#include "diff.h" #include "diff.h"
#include "fileops.h" #include "fileops.h"
#include "config.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) static void diff_delta__free(git_diff_delta *delta)
{ {
Expand Down Expand Up @@ -143,6 +187,9 @@ static int diff_delta__from_one(
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return 0; return 0;


if (!diff_path_matches_pathspec(diff, entry->path))
return 0;

delta = diff_delta__alloc(diff, status, entry->path); delta = diff_delta__alloc(diff, status, entry->path);
GITERR_CHECK_ALLOC(delta); GITERR_CHECK_ALLOC(delta);


Expand Down Expand Up @@ -246,6 +293,7 @@ static git_diff_list *git_diff_list_alloc(
git_repository *repo, const git_diff_options *opts) git_repository *repo, const git_diff_options *opts)
{ {
git_config *cfg; git_config *cfg;
size_t i;
git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
if (diff == NULL) if (diff == NULL)
return NULL; return NULL;
Expand All @@ -269,6 +317,7 @@ static git_diff_list *git_diff_list_alloc(
return diff; return diff;


memcpy(&diff->opts, opts, sizeof(git_diff_options)); memcpy(&diff->opts, opts, sizeof(git_diff_options));
memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec));


diff->opts.src_prefix = diff_strdup_prefix( diff->opts.src_prefix = diff_strdup_prefix(
opts->src_prefix ? opts->src_prefix : DIFF_SRC_PREFIX_DEFAULT); opts->src_prefix ? opts->src_prefix : DIFF_SRC_PREFIX_DEFAULT);
Expand All @@ -287,21 +336,45 @@ static git_diff_list *git_diff_list_alloc(
if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0) if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0)
goto fail; 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; return diff;


fail: fail:
git_vector_free(&diff->deltas); git_diff_list_free(diff);
git__free(diff->opts.src_prefix);
git__free(diff->opts.dst_prefix);
git__free(diff);
return NULL; return NULL;
} }


void git_diff_list_free(git_diff_list *diff) void git_diff_list_free(git_diff_list *diff)
{ {
git_diff_delta *delta; git_diff_delta *delta;
git_attr_fnmatch *match;
unsigned int i; unsigned int i;


if (!diff) if (!diff)
Expand All @@ -312,6 +385,17 @@ void git_diff_list_free(git_diff_list *diff)
diff->deltas.contents[i] = NULL; diff->deltas.contents[i] = NULL;
} }
git_vector_free(&diff->deltas); 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.src_prefix);
git__free(diff->opts.dst_prefix); git__free(diff->opts.dst_prefix);
git__free(diff); git__free(diff);
Expand Down Expand Up @@ -366,6 +450,9 @@ static int maybe_modified(


GIT_UNUSED(old); GIT_UNUSED(old);


if (!diff_path_matches_pathspec(diff, oitem->path))
return 0;

/* on platforms with no symlinks, promote plain files to symlinks */ /* on platforms with no symlinks, promote plain files to symlinks */
if (S_ISLNK(omode) && S_ISREG(nmode) && if (S_ISLNK(omode) && S_ISREG(nmode) &&
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
Expand Down
1 change: 1 addition & 0 deletions src/diff.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum {
struct git_diff_list { struct git_diff_list {
git_repository *repo; git_repository *repo;
git_diff_options opts; git_diff_options opts;
git_vector pathspec;
git_vector deltas; /* vector of git_diff_file_delta */ git_vector deltas; /* vector of git_diff_file_delta */
git_iterator_type_t old_src; git_iterator_type_t old_src;
git_iterator_type_t new_src; git_iterator_type_t new_src;
Expand Down
1 change: 1 addition & 0 deletions src/status.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ int git_status_foreach(
{ {
git_status_options opts; git_status_options opts;


memset(&opts, 0, sizeof(opts));
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNTRACKED |
Expand Down
29 changes: 29 additions & 0 deletions src/util.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -31,6 +31,35 @@ void git_strarray_free(git_strarray *array)
git__free(array->strings); 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 git__fnmatch(const char *pattern, const char *name, int flags)
{ {
int ret; int ret;
Expand Down
6 changes: 4 additions & 2 deletions tests-clar/attr/file.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void test_attr_file__simple_read(void)
cl_assert(rule != NULL); cl_assert(rule != NULL);
cl_assert_strequal("*", rule->match.pattern); cl_assert_strequal("*", rule->match.pattern);
cl_assert(rule->match.length == 1); 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); cl_assert(rule->assigns.length == 1);
assign = get_assign(rule, 0); assign = get_assign(rule, 0);
Expand Down Expand Up @@ -74,14 +74,16 @@ void test_attr_file__match_variants(void)


rule = get_rule(4); rule = get_rule(4);
cl_assert_strequal("pat4.*", rule->match.pattern); 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); rule = get_rule(5);
cl_assert_strequal("*.pat5", rule->match.pattern); cl_assert_strequal("*.pat5", rule->match.pattern);
cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);


rule = get_rule(7); rule = get_rule(7);
cl_assert_strequal("pat7[a-e]??[xyz]", rule->match.pattern); cl_assert_strequal("pat7[a-e]??[xyz]", rule->match.pattern);
cl_assert(rule->assigns.length == 1); cl_assert(rule->assigns.length == 1);
cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
assign = get_assign(rule,0); assign = get_assign(rule,0);
cl_assert_strequal("attr7", assign->name); cl_assert_strequal("attr7", assign->name);
cl_assert(GIT_ATTR_TRUE(assign->value)); cl_assert(GIT_ATTR_TRUE(assign->value));
Expand Down
73 changes: 73 additions & 0 deletions tests-clar/diff/workdir.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -164,6 +164,79 @@ void test_diff_workdir__to_tree(void)
git_tree_free(b); 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 /* PREPARATION OF TEST DATA
* *
* Since there is no command line equivalent of git_diff_workdir_to_tree, * Since there is no command line equivalent of git_diff_workdir_to_tree,
Expand Down