8000 Merge pull request #1367 from yorah/fix/pathspecs_behaviour · phatblat/libgit2@a1161a1 · GitHub
[go: up one dir, main page]

Skip to content

Commit a1161a1

Browse files
author
Vicent Martí
committed
Merge pull request libgit2#1367 from yorah/fix/pathspecs_behaviour
Correctly return matched pathspec when passing "*" or "."
2 parents f6a49f3 + c7efe0b commit a1161a1

File tree

6 files changed

+268
-175
lines changed

6 files changed

+268
-175
lines changed

src/attr_file.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
1010
static void git_attr_rule__clear(git_attr_rule *rule);
11+
static bool parse_optimized_patterns(
12+
git_attr_fnmatch *spec,
13+
git_pool *pool,
14+
const char *pattern);
1115

1216
int git_attr_file__new(
1317
git_attr_file **attrs_ptr,
@@ -296,7 +300,6 @@ void git_attr_path__free(git_attr_path *info)
296300
info->basename = NULL;
297301
}
298302

299-
300303
/*
301304
* From gitattributes(5):
302305
*
@@ -345,6 +348,9 @@ int git_attr_fnmatch__parse(
345348

346349
assert(spec && base && *base);
347350

351+
if (parse_optimized_patterns(spec, pool, *base))
352+
return 0;
353+
348354
spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE);
349355
allow_space = (spec->flags != 0);
350356

@@ -430,6 +436,22 @@ int git_attr_fnmatch__parse(
430436
return 0;
431437
}
432438

439+
static bool parse_optimized_patterns(
440+
git_attr_fnmatch *spec,
441+
git_pool *pool,
442+
const char *pattern)
443+
{
444+
if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
445+
spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
446+
spec->pattern = git_pool_strndup(pool, pattern, 1);
447+
spec->length = 1;
448+
449+
return true;
450+
}
451+
452+
return false;
453+
}
454+
433455
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
434456
{
435457
const git_attr_name *a = a_raw;

src/attr_file.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#define GIT_ATTR_FNMATCH_HASWILD (1U << 5)
2828
#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)
2929
#define GIT_ATTR_FNMATCH_ICASE (1U << 7)
30+
#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
3031

3132
extern const char *git_attr__true;
3233
extern const char *git_attr__false;

src/pathspec.c

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,20 @@ char *git_pathspec_prefix(const git_strarray *pathspec)
3838
}
3939

4040
/* is there anything in the spec that needs to be filtered on */
41-
bool git_pathspec_is_interesting(const git_strarray *pathspec)
41+
bool git_pathspec_is_empty(const git_strarray *pathspec)
4242
{
43-
const char *str;
43+
size_t i;
4444

45-
if (pathspec == NULL || pathspec->count == 0)
46-
return false;
47-
if (pathspec->count > 1)
45+
if (pathspec == NULL)
4846
return true;
4947

50-
str = pathspec->strings[0];
51-
if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
52-
return false;
48+
for (i = 0; i < pathspec->count; ++i) {
49+
const char *str = pathspec->strings[i];
50+
51+
if (str && str[0])
52+
return false;
53+
}
54+
5355
return true;
5456
}
5557

@@ -61,7 +63,7 @@ int git_pathspec_init(
6163

6264
memset(vspec, 0, sizeof(*vspec));
6365

64-
if (!git_pathspec_is_interesting(strspec))
66+
if (git_pathspec_is_empty(strspec))
6567
return 0;
6668

6769
if (git_vector_init(vspec, strspec->count, NULL) < 0)
@@ -138,7 +140,10 @@ bool git_pathspec_match_path(
138140
}
139141

140142
git_vector_foreach(vspec, i, match) {
141-
int result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
143+
int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;
144+
145+
if (result == FNM_NOMATCH)
146+
result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
142147

143148
if (fnmatch_flags >= 0 && result == FNM_NOMATCH)
144149
result = p_fnmatch(match->pattern, path, fnmatch_flags);

src/pathspec.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
extern char *git_pathspec_prefix(const git_strarray *pathspec);
1717

1818
/* is there anything in the spec that needs to be filtered on */
19-
extern bool git_pathspec_is_interesting(const git_strarray *pathspec);
19+
extern bool git_pathspec_is_empty(const git_strarray *pathspec);
2020

2121
/* build a vector of fnmatch patterns to evaluate efficiently */
2222
extern int git_pathspec_init(

tests-clar/diff/notify.c

Lines changed: 228 additions & 0 deletions
< 10000 td data-grid-cell-id="diff-17db2dcf8044f8874844c75f3c66ed22a1e7354286fff1ea633388f89f505a4e-empty-162-2" data-line-anchor="diff-17db2dcf8044f8874844c75f3c66ed22a1e7354286fff1ea633388f89f505a4eR162" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-additionLine-bgColor, var(--diffBlob-addition-bgColor-line));padding-right:24px" tabindex="-1" valign="top" class="focusable-grid-cell diff-text-cell right-side-diff-cell left-side">+
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#include "clar_libgit2.h"
2+
#include "diff_helpers.h"
3+
4+
static git_repository *g_repo = NULL;
5+
6+
void test_diff_notify__initialize(void)
7+
{
8+
}
9+
10+
void test_diff_notify__cleanup(void)
11+
{
12+
cl_git_sandbox_cleanup();
13+
}
14+
15+
static int assert_called_notifications(
16+
const git_diff_list *diff_so_far,
17+
const git_diff_delta *delta_to_add,
18+
const char *matched_pathspec,
19+
void *payload)
20+
{
21+
bool found = false;
22+
notify_expected *exp = (notify_expected*)payload;
23+
notify_expected *e;;
24+
25+
GIT_UNUSED(diff_so_far);
26+
27+
for (e = exp; e->path != NULL; e++) {
28+
if (strcmp(e->path, delta_to_add->new_file.path))
29+
continue;
30+
31+
cl_assert_equal_s(e->matched_pathspec, matched_pathspec);
32+
33+
found = true;
34+
break;
35+
}
36+
37+
cl_assert(found);
38+
return 0;
39+
}
40+
41+
static void test_notify(
42+
char **searched_pathspecs,
43+
int pathspecs_count,
44+
notify_expected *expected_matched_pathspecs,
45+
int expected_diffed_files_count)
46+
{
47+
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
48+
git_diff_list *diff = NULL;
49+
diff_expects exp;
50+
51+
g_repo = cl_git_sandbox_init("status");
52+
53+
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
54+
opts.notify_cb = assert_called_notifications;
55+
opts.pathspec.strings = searched_pathspecs;
56+
opts.pathspec.count = pathspecs_count;
57+
58+
opts.notify_payload = expected_matched_pathspecs;
59+
memset(&exp, 0, sizeof(exp));
60+
61+
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
62+
cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
63+
64+
cl_assert_equal_i(expected_diffed_files_count, exp.files);
65+
66+
git_diff_list_free(diff);
67+
}
68+
69+
void test_diff_notify__notify_single_pathspec(void)
70+
{
71+
char *searched_pathspecs[] = {
72+
"*_deleted",
73+
};
74+
notify_expected expected_matched_pathspecs[] = {
75+
{ "file_deleted", "*_deleted" },
76+
{ "staged_changes_file_deleted", "*_deleted" },
77+
{ NULL, NULL }
78+
};
79+
80+
test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 2);
81+
}
82+
83+
void test_diff_notify__notify_multiple_pathspec(void)
84+
{
85+
char *searched_pathspecs[] = {
86+
"staged_changes_cant_find_me",
87+
"subdir/modified_cant_find_me",
88+
"subdir/*",
89+
"staged*"
90+
};
91+
notify_expected expected_matched_pathspecs[] = {
92+
{ "staged_changes_file_deleted", "staged*" },
93+
{ "staged_changes_modified_file", "staged*" },
94+
{ "staged_delete_modified_file", "staged*" },
95+
{ "staged_new_file_deleted_file", "staged*" },
96+
{ "staged_new_file_modified_file", "staged*" },
97+
{ "subdir/deleted_file", "subdir/*" },
98+
{ "subdir/modified_file", "subdir/*" },
99+
{ "subdir/new_file", "subdir/*" },
100+
{ NULL, NULL }
101+
};
102+
103+
test_notify(searched_pathspecs, 4, expected_matched_pathspecs, 8);
104+
}
105+
106+
void test_diff_notify__notify_catchall_with_empty_pathspecs(void)
107+
{
108+
char *searched_pathspecs[] = {
109+
"",
110+
""
111+
};
112+
notify_expected expected_matched_pathspecs[] = {
113+
{ "file_deleted", NULL },
114+
{ "ignored_file", NULL },
115+
{ "modified_file", NULL },
116+
{ "new_file", NULL },
117+
{ "\xe8\xbf\x99", NULL },
118+
{ "staged_changes_file_deleted", NULL },
119+
{ "staged_changes_modified_file", NULL },
120+
{ "staged_delete_modified_file", NULL },
121+
{ "staged_new_file_deleted_file", NULL },
122+
{ "staged_new_file_modified_file", NULL },
123+
{ "subdir/deleted_file", NULL },
124+
{ "subdir/modified_file", NULL },
125+
{ "subdir/new_file", NULL },
126+
{ NULL, NULL }
127+
};
128+
129+
test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13);
130+
}
131+
132+
void test_diff_notify__notify_catchall(void)
133+
{
134+
char *searched_pathspecs[] = {
135+
"*",
136+
};
137+
notify_expected expected_matched_pathspecs[] = {
138+
{ "file_deleted", "*" },
139+
{ "ignored_file", "*" },
140+
{ "modified_file", "*" },
141+
{ "new_file", "*" },
142+
{ "\xe8\xbf\x99", "*" },
143+
{ "staged_changes_file_deleted", "*" },
144+
{ "staged_changes_modified_file", "*" },
145+
{ "staged_delete_modified_file", "*" },
146+
{ "staged_new_file_deleted_file", "*" },
147+
{ "staged_new_file_modified_file", "*" },
148+
{ "subdir/deleted_file", "*" },
149+
{ "subdir/modified_file", "*" },
150+
{ "subdir/new_file", "*" },
151+
{ NULL, NULL }
152+
};
153+
154+
test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13);
155+
}
156+
157+
static int abort_diff(
158+
const git_diff_list *diff_so_far,
159+
const git_diff_delta *delta_to_add,
160+
const char *matched_pathspec,
161+
void *payload)
162
163+
GIT_UNUSED(diff_so_far);
164+
GIT_UNUSED(delta_to_add);
165+
GIT_UNUSED(matched_pathspec);
166+
GIT_UNUSED(payload);
167+
168+
return -42;
169+
}
170+
171+
void test_diff_notify__notify_cb_can_abort_diff(void)
172+
{
173+
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
174+
git_diff_list *diff = NULL;
175+
char *pathspec = NULL;
176+
177+
g_repo = cl_git_sandbox_init("status");
178+
179+
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
180+
opts.notify_cb = abort_diff;
181+
opts.pathspec.strings = &pathspec;
182+
opts.pathspec.count = 1;
183+
184+
pathspec = "file_deleted";
185+
cl_git_fail(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
186+
187+
pathspec = "staged_changes_modified_file";
188+
cl_git_fail(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
189+
}
190+
191+
static int filter_all(
192+
const git_diff_list *diff_so_far,
193+
const git_diff_delta *delta_to_add,
194+
const char *matched_pathspec,
195+
void *payload)
196+
{
197+
GIT_UNUSED(diff_so_far);
198+
GIT_UNUSED(delta_to_add);
199+
GIT_UNUSED(matched_pathspec);
200+
GIT_UNUSED(payload);
201+
202+
return 42;
203+
}
204+
205+
void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void)
206+
{
207+
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
208+
git_diff_list *diff = NULL;
209+
char *pathspec = NULL;
210+
diff_expects exp;
211+
212+
g_repo = cl_git_sandbox_init("status");
213+
214+
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
215+
opts.notify_cb = filter_all;
216+
opts.pathspec.strings = &pathspec;
217+
opts.pathspec.count = 1;
218+
219+
pathspec = "*_deleted";
220+
memset(&exp, 0, sizeof(exp));
221+
222+
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
223+
cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
224+
225+
cl_assert_equal_i(0, exp.files);
226+
227+
git_diff_list_free(diff);
228+
}

0 commit comments

Comments
 (0)
0