mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-21 16:18:46 +00:00
Merge pull request #627 from arrbee/diff-with-pathspec
Diff with pathspec
This commit is contained in:
commit
e77e53edb3
@ -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;
|
||||
|
97
src/diff.c
97
src/diff.c
@ -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 |
|
||||
|
29
src/util.c
29
src/util.c
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user