]>
Commit | Line | Data |
---|---|---|
2e3d4b96 | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
2e3d4b96 RB |
3 | * |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
eae0bfdc PP |
8 | #include "pathspec.h" |
9 | ||
d2ce27dd | 10 | #include "git2/pathspec.h" |
2b672d5b | 11 | #include "git2/diff.h" |
2e3d4b96 | 12 | #include "attr_file.h" |
d2ce27dd RB |
13 | #include "iterator.h" |
14 | #include "repository.h" | |
15 | #include "index.h" | |
2b672d5b RB |
16 | #include "bitvec.h" |
17 | #include "diff.h" | |
22a2d3d5 | 18 | #include "wildmatch.h" |
2e3d4b96 RB |
19 | |
20 | /* what is the common non-wildcard prefix for all items in the pathspec */ | |
21 | char *git_pathspec_prefix(const git_strarray *pathspec) | |
22 | { | |
e579e0f7 | 23 | git_str prefix = GIT_STR_INIT; |
2e3d4b96 RB |
24 | const char *scan; |
25 | ||
26 | if (!pathspec || !pathspec->count || | |
e579e0f7 | 27 | git_str_common_prefix(&prefix, pathspec->strings, pathspec->count) < 0) |
2e3d4b96 RB |
28 | return NULL; |
29 | ||
30 | /* diff prefix will only be leading non-wildcards */ | |
31 | for (scan = prefix.ptr; *scan; ++scan) { | |
32 | if (git__iswildcard(*scan) && | |
33 | (scan == prefix.ptr || (*(scan - 1) != '\\'))) | |
34 | break; | |
35 | } | |
e579e0f7 | 36 | git_str_truncate(&prefix, scan - prefix.ptr); |
2e3d4b96 RB |
37 | |
38 | if (prefix.size <= 0) { | |
e579e0f7 | 39 | git_str_dispose(&prefix); |
2e3d4b96 RB |
40 | return NULL; |
41 | } | |
42 | ||
e579e0f7 | 43 | git_str_unescape(&prefix); |
2e3d4b96 | 44 | |
e579e0f7 | 45 | return git_str_detach(&prefix); |
2e3d4b96 RB |
46 | } |
47 | ||
48 | /* is there anything in the spec that needs to be filtered on */ | |
0d32f39e | 49 | bool git_pathspec_is_empty(const git_strarray *pathspec) |
2e3d4b96 | 50 | { |
0d32f39e | 51 | size_t i; |
2e3d4b96 | 52 | |
0d32f39e | 53 | if (pathspec == NULL) |
2e3d4b96 RB |
54 | return true; |
55 | ||
0d32f39e | 56 | for (i = 0; i < pathspec->count; ++i) { |
57 | const char *str = pathspec->strings[i]; | |
58 | ||
59 | if (str && str[0]) | |
60 | return false; | |
61 | } | |
62 | ||
2e3d4b96 RB |
63 | return true; |
64 | } | |
65 | ||
66 | /* build a vector of fnmatch patterns to evaluate efficiently */ | |
d2ce27dd | 67 | int git_pathspec__vinit( |
2e3d4b96 RB |
68 | git_vector *vspec, const git_strarray *strspec, git_pool *strpool) |
69 | { | |
70 | size_t i; | |
71 | ||
72 | memset(vspec, 0, sizeof(*vspec)); | |
73 | ||
0d32f39e | 74 | if (git_pathspec_is_empty(strspec)) |
2e3d4b96 RB |
75 | return 0; |
76 | ||
77 | if (git_vector_init(vspec, strspec->count, NULL) < 0) | |
78 | return -1; | |
79 | ||
80 | for (i = 0; i < strspec->count; ++i) { | |
81 | int ret; | |
82 | const char *pattern = strspec->strings[i]; | |
83 | git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); | |
84 | if (!match) | |
85 | return -1; | |
86 | ||
22a2d3d5 | 87 | match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; |
2e3d4b96 | 88 | |
4ba64794 | 89 | ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); |
2e3d4b96 RB |
90 | if (ret == GIT_ENOTFOUND) { |
91 | git__free(match); | |
92 | continue; | |
63170bca AS |
93 | } else if (ret < 0) { |
94 | git__free(match); | |
2e3d4b96 | 95 | return ret; |
63170bca | 96 | } |
2e3d4b96 RB |
97 | |
98 | if (git_vector_insert(vspec, match) < 0) | |
99 | return -1; | |
100 | } | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | /* free data from the pathspec vector */ | |
d2ce27dd | 106 | void git_pathspec__vfree(git_vector *vspec) |
2e3d4b96 | 107 | { |
9cfce273 | 108 | git_vector_free_deep(vspec); |
2e3d4b96 RB |
109 | } |
110 | ||
d2ce27dd | 111 | struct pathspec_match_context { |
22a2d3d5 | 112 | int wildmatch_flags; |
d2ce27dd RB |
113 | int (*strcomp)(const char *, const char *); |
114 | int (*strncomp)(const char *, const char *, size_t); | |
115 | }; | |
116 | ||
117 | static void pathspec_match_context_init( | |
118 | struct pathspec_match_context *ctxt, | |
119 | bool disable_fnmatch, | |
120 | bool casefold) | |
121 | { | |
122 | if (disable_fnmatch) | |
22a2d3d5 | 123 | ctxt->wildmatch_flags = -1; |
d2ce27dd | 124 | else if (casefold) |
22a2d3d5 | 125 | ctxt->wildmatch_flags = WM_CASEFOLD; |
d2ce27dd | 126 | else |
22a2d3d5 | 127 | ctxt->wildmatch_flags = 0; |
d2ce27dd RB |
128 | |
129 | if (casefold) { | |
130 | ctxt->strcomp = git__strcasecmp; | |
131 | ctxt->strncomp = git__strncasecmp; | |
132 | } else { | |
133 | ctxt->strcomp = git__strcmp; | |
134 | ctxt->strncomp = git__strncmp; | |
135 | } | |
136 | } | |
137 | ||
138 | static int pathspec_match_one( | |
139 | const git_attr_fnmatch *match, | |
140 | struct pathspec_match_context *ctxt, | |
141 | const char *path) | |
142 | { | |
22a2d3d5 | 143 | int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH; |
d2ce27dd | 144 | |
22a2d3d5 UG |
145 | if (result == WM_NOMATCH) |
146 | result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0; | |
d2ce27dd | 147 | |
22a2d3d5 UG |
148 | if (ctxt->wildmatch_flags >= 0 && result == WM_NOMATCH) |
149 | result = wildmatch(match->pattern, path, ctxt->wildmatch_flags); | |
d2ce27dd RB |
150 | |
151 | /* if we didn't match, look for exact dirname prefix match */ | |
22a2d3d5 | 152 | if (result == WM_NOMATCH && |
d2ce27dd RB |
153 | (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && |
154 | ctxt->strncomp(path, match->pattern, match->length) == 0 && | |
155 | path[match->length] == '/') | |
156 | result = 0; | |
157 | ||
4ba64794 RB |
158 | /* if we didn't match and this is a negative match, check for exact |
159 | * match of filename with leading '!' | |
160 | */ | |
22a2d3d5 | 161 | if (result == WM_NOMATCH && |
4ba64794 RB |
162 | (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && |
163 | *path == '!' && | |
b7b77def RB |
164 | ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && |
165 | (!path[match->length + 1] || path[match->length + 1] == '/')) | |
4ba64794 RB |
166 | return 1; |
167 | ||
d2ce27dd RB |
168 | if (result == 0) |
169 | return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; | |
170 | return -1; | |
171 | } | |
172 | ||
2b672d5b RB |
173 | static int git_pathspec__match_at( |
174 | size_t *matched_at, | |
175 | const git_vector *vspec, | |
176 | struct pathspec_match_context *ctxt, | |
177 | const char *path0, | |
178 | const char *path1) | |
179 | { | |
180 | int result = GIT_ENOTFOUND; | |
181 | size_t i = 0; | |
182 | const git_attr_fnmatch *match; | |
183 | ||
184 | git_vector_foreach(vspec, i, match) { | |
185 | if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) | |
186 | break; | |
187 | if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) | |
188 | break; | |
189 | } | |
190 | ||
191 | *matched_at = i; | |
192 | return result; | |
193 | } | |
194 | ||
2e3d4b96 | 195 | /* match a path against the vectorized pathspec */ |
d2ce27dd RB |
196 | bool git_pathspec__match( |
197 | const git_vector *vspec, | |
943700ec | 198 | const char *path, |
199 | bool disable_fnmatch, | |
200 | bool casefold, | |
d2ce27dd RB |
201 | const char **matched_pathspec, |
202 | size_t *matched_at) | |
2e3d4b96 | 203 | { |
2b672d5b RB |
204 | int result; |
205 | size_t pos; | |
d2ce27dd | 206 | struct pathspec_match_context ctxt; |
2e3d4b96 | 207 | |
943700ec | 208 | if (matched_pathspec) |
209 | *matched_pathspec = NULL; | |
d2ce27dd RB |
210 | if (matched_at) |
211 | *matched_at = GIT_PATHSPEC_NOMATCH; | |
943700ec | 212 | |
2e3d4b96 RB |
213 | if (!vspec || !vspec->length) |
214 | return true; | |
215 | ||
d2ce27dd | 216 | pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); |
2e3d4b96 | 217 | |
2b672d5b RB |
218 | result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); |
219 | if (result >= 0) { | |
220 | if (matched_pathspec) { | |
221 | const git_attr_fnmatch *match = git_vector_get(vspec, pos); | |
222 | *matched_pathspec = match->pattern; | |
d2ce27dd | 223 | } |
2b672d5b RB |
224 | |
225 | if (matched_at) | |
226 | *matched_at = pos; | |
2e3d4b96 RB |
227 | } |
228 | ||
2b672d5b | 229 | return (result > 0); |
d2ce27dd | 230 | } |
1fed6b07 | 231 | |
2e3d4b96 | 232 | |
d2ce27dd RB |
233 | int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) |
234 | { | |
235 | int error = 0; | |
2e3d4b96 | 236 | |
d2ce27dd | 237 | memset(ps, 0, sizeof(*ps)); |
2e3d4b96 | 238 | |
d2ce27dd RB |
239 | ps->prefix = git_pathspec_prefix(paths); |
240 | ||
22a2d3d5 UG |
241 | if ((error = git_pool_init(&ps->pool, 1)) < 0 || |
242 | (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) | |
d2ce27dd RB |
243 | git_pathspec__clear(ps); |
244 | ||
245 | return error; | |
246 | } | |
247 | ||
248 | void git_pathspec__clear(git_pathspec *ps) | |
249 | { | |
250 | git__free(ps->prefix); | |
251 | git_pathspec__vfree(&ps->pathspec); | |
252 | git_pool_clear(&ps->pool); | |
253 | memset(ps, 0, sizeof(*ps)); | |
254 | } | |
255 | ||
256 | int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) | |
257 | { | |
258 | int error = 0; | |
259 | git_pathspec *ps = git__malloc(sizeof(git_pathspec)); | |
ac3d33df | 260 | GIT_ERROR_CHECK_ALLOC(ps); |
d2ce27dd RB |
261 | |
262 | if ((error = git_pathspec__init(ps, pathspec)) < 0) { | |
263 | git__free(ps); | |
264 | return error; | |
265 | } | |
266 | ||
267 | GIT_REFCOUNT_INC(ps); | |
268 | *out = ps; | |
269 | return 0; | |
270 | } | |
271 | ||
272 | static void pathspec_free(git_pathspec *ps) | |
273 | { | |
274 | git_pathspec__clear(ps); | |
275 | git__free(ps); | |
276 | } | |
277 | ||
278 | void git_pathspec_free(git_pathspec *ps) | |
279 | { | |
280 | if (!ps) | |
281 | return; | |
282 | GIT_REFCOUNT_DEC(ps, pathspec_free); | |
283 | } | |
943700ec | 284 | |
d2ce27dd RB |
285 | int git_pathspec_matches_path( |
286 | const git_pathspec *ps, uint32_t flags, const char *path) | |
287 | { | |
288 | bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; | |
289 | bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; | |
290 | ||
c25aa7cd PP |
291 | GIT_ASSERT_ARG(ps); |
292 | GIT_ASSERT_ARG(path); | |
d2ce27dd RB |
293 | |
294 | return (0 != git_pathspec__match( | |
295 | &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); | |
296 | } | |
297 | ||
298 | static void pathspec_match_free(git_pathspec_match_list *m) | |
299 | { | |
dc5fe00c BE |
300 | if (!m) |
301 | return; | |
302 | ||
d2ce27dd RB |
303 | git_pathspec_free(m->pathspec); |
304 | m->pathspec = NULL; | |
305 | ||
306 | git_array_clear(m->matches); | |
307 | git_array_clear(m->failures); | |
308 | git_pool_clear(&m->pool); | |
309 | git__free(m); | |
310 | } | |
311 | ||
2b672d5b RB |
312 | static git_pathspec_match_list *pathspec_match_alloc( |
313 | git_pathspec *ps, int datatype) | |
d2ce27dd RB |
314 | { |
315 | git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); | |
636af219 JG |
316 | if (!m) |
317 | return NULL; | |
318 | ||
22a2d3d5 UG |
319 | if (git_pool_init(&m->pool, 1) < 0) |
320 | return NULL; | |
1e5e02b4 | 321 | |
d2ce27dd RB |
322 | /* need to keep reference to pathspec and increment refcount because |
323 | * failures array stores pointers to the pattern strings of the | |
324 | * pathspec that had no matches | |
325 | */ | |
326 | GIT_REFCOUNT_INC(ps); | |
327 | m->pathspec = ps; | |
2b672d5b | 328 | m->datatype = datatype; |
d2ce27dd RB |
329 | |
330 | return m; | |
331 | } | |
332 | ||
2b672d5b RB |
333 | GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) |
334 | { | |
335 | if (!git_bitvec_get(used, pos)) { | |
336 | git_bitvec_set(used, pos, true); | |
337 | return 1; | |
338 | } | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
343 | static size_t pathspec_mark_remaining( | |
344 | git_bitvec *used, | |
345 | git_vector *patterns, | |
346 | struct pathspec_match_context *ctxt, | |
347 | size_t start, | |
348 | const char *path0, | |
349 | const char *path1) | |
350 | { | |
351 | size_t count = 0; | |
352 | ||
353 | if (path1 == path0) | |
354 | path1 = NULL; | |
355 | ||
356 | for (; start < patterns->length; ++start) { | |
357 | const git_attr_fnmatch *pat = git_vector_get(patterns, start); | |
358 | ||
359 | if (git_bitvec_get(used, start)) | |
360 | continue; | |
361 | ||
362 | if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) | |
363 | count += pathspec_mark_pattern(used, start); | |
364 | else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) | |
365 | count += pathspec_mark_pattern(used, start); | |
366 | } | |
367 | ||
368 | return count; | |
369 | } | |
370 | ||
371 | static int pathspec_build_failure_array( | |
372 | git_pathspec_string_array_t *failures, | |
373 | git_vector *patterns, | |
374 | git_bitvec *used, | |
375 | git_pool *pool) | |
d2ce27dd | 376 | { |
2b672d5b RB |
377 | size_t pos; |
378 | char **failed; | |
379 | const git_attr_fnmatch *pat; | |
380 | ||
381 | for (pos = 0; pos < patterns->length; ++pos) { | |
382 | if (git_bitvec_get(used, pos)) | |
383 | continue; | |
384 | ||
385 | if ((failed = git_array_alloc(*failures)) == NULL) | |
386 | return -1; | |
387 | ||
388 | pat = git_vector_get(patterns, pos); | |
389 | ||
390 | if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) | |
391 | return -1; | |
d2ce27dd | 392 | } |
2b672d5b RB |
393 | |
394 | return 0; | |
d2ce27dd RB |
395 | } |
396 | ||
397 | static int pathspec_match_from_iterator( | |
398 | git_pathspec_match_list **out, | |
399 | git_iterator *iter, | |
400 | uint32_t flags, | |
401 | git_pathspec *ps) | |
402 | { | |
403 | int error = 0; | |
a8b5f116 | 404 | git_pathspec_match_list *m = NULL; |
d2ce27dd RB |
405 | const git_index_entry *entry = NULL; |
406 | struct pathspec_match_context ctxt; | |
407 | git_vector *patterns = &ps->pathspec; | |
2b672d5b RB |
408 | bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; |
409 | bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; | |
d2ce27dd RB |
410 | size_t pos, used_ct = 0, found_files = 0; |
411 | git_index *index = NULL; | |
2b672d5b | 412 | git_bitvec used_patterns; |
d2ce27dd RB |
413 | char **file; |
414 | ||
2b672d5b RB |
415 | if (git_bitvec_init(&used_patterns, patterns->length) < 0) |
416 | return -1; | |
417 | ||
a8b5f116 | 418 | if (out) { |
2b672d5b | 419 | *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); |
ac3d33df | 420 | GIT_ERROR_CHECK_ALLOC(m); |
a8b5f116 | 421 | } |
d2ce27dd | 422 | |
684b35c4 | 423 | if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0) |
d2ce27dd RB |
424 | goto done; |
425 | ||
22a2d3d5 | 426 | if (git_iterator_type(iter) == GIT_ITERATOR_WORKDIR && |
d2ce27dd RB |
427 | (error = git_repository_index__weakptr( |
428 | &index, git_iterator_owner(iter))) < 0) | |
429 | goto done; | |
430 | ||
2b672d5b RB |
431 | pathspec_match_context_init( |
432 | &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, | |
433 | git_iterator_ignore_case(iter)); | |
d2ce27dd RB |
434 | |
435 | while (!(error = git_iterator_advance(&entry, iter))) { | |
2b672d5b RB |
436 | /* search for match with entry->path */ |
437 | int result = git_pathspec__match_at( | |
438 | &pos, patterns, &ctxt, entry->path, NULL); | |
d2ce27dd RB |
439 | |
440 | /* no matches for this path */ | |
441 | if (result < 0) | |
442 | continue; | |
443 | ||
444 | /* if result was a negative pattern match, then don't list file */ | |
445 | if (!result) { | |
2b672d5b | 446 | used_ct += pathspec_mark_pattern(&used_patterns, pos); |
d2ce27dd RB |
447 | continue; |
448 | } | |
449 | ||
2b672d5b | 450 | /* check if path is ignored and untracked */ |
d2ce27dd RB |
451 | if (index != NULL && |
452 | git_iterator_current_is_ignored(iter) && | |
52bb0476 | 453 | git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0) |
d2ce27dd RB |
454 | continue; |
455 | ||
456 | /* mark the matched pattern as used */ | |
2b672d5b | 457 | used_ct += pathspec_mark_pattern(&used_patterns, pos); |
d2ce27dd RB |
458 | ++found_files; |
459 | ||
460 | /* if find_failures is on, check if any later patterns also match */ | |
2b672d5b RB |
461 | if (find_failures && used_ct < patterns->length) |
462 | used_ct += pathspec_mark_remaining( | |
463 | &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); | |
d2ce27dd RB |
464 | |
465 | /* if only looking at failures, exit early or just continue */ | |
a8b5f116 | 466 | if (failures_only || !out) { |
d2ce27dd RB |
467 | if (used_ct == patterns->length) |
468 | break; | |
469 | continue; | |
470 | } | |
471 | ||
472 | /* insert matched path into matches array */ | |
2b672d5b | 473 | if ((file = (char **)git_array_alloc(m->matches)) == NULL || |
d2ce27dd RB |
474 | (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { |
475 | error = -1; | |
476 | goto done; | |
943700ec | 477 | } |
2e3d4b96 RB |
478 | } |
479 | ||
d2ce27dd RB |
480 | if (error < 0 && error != GIT_ITEROVER) |
481 | goto done; | |
482 | error = 0; | |
483 | ||
484 | /* insert patterns that had no matches into failures array */ | |
2b672d5b RB |
485 | if (find_failures && used_ct < patterns->length && |
486 | (error = pathspec_build_failure_array( | |
487 | &m->failures, patterns, &used_patterns, &m->pool)) < 0) | |
488 | goto done; | |
d2ce27dd RB |
489 | |
490 | /* if every pattern failed to match, then we have failed */ | |
491 | if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { | |
ac3d33df | 492 | git_error_set(GIT_ERROR_INVALID, "no matching files were found"); |
d2ce27dd RB |
493 | error = GIT_ENOTFOUND; |
494 | } | |
495 | ||
496 | done: | |
2b672d5b | 497 | git_bitvec_free(&used_patterns); |
d2ce27dd RB |
498 | |
499 | if (error < 0) { | |
500 | pathspec_match_free(m); | |
a8b5f116 | 501 | if (out) *out = NULL; |
d2ce27dd RB |
502 | } |
503 | ||
504 | return error; | |
2e3d4b96 RB |
505 | } |
506 | ||
d2ce27dd RB |
507 | static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) |
508 | { | |
509 | git_iterator_flag_t f = 0; | |
510 | ||
511 | if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) | |
512 | f |= GIT_ITERATOR_IGNORE_CASE; | |
513 | else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) | |
514 | f |= GIT_ITERATOR_DONT_IGNORE_CASE; | |
e91f9a8f | 515 | |
d2ce27dd RB |
516 | return f; |
517 | } | |
518 | ||
519 | int git_pathspec_match_workdir( | |
520 | git_pathspec_match_list **out, | |
521 | git_repository *repo, | |
522 | uint32_t flags, | |
523 | git_pathspec *ps) | |
e91f9a8f | 524 | { |
d2ce27dd | 525 | git_iterator *iter; |
ed1c6446 ET |
526 | git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; |
527 | int error = 0; | |
d2ce27dd | 528 | |
c25aa7cd | 529 | GIT_ASSERT_ARG(repo); |
e91f9a8f | 530 | |
ed1c6446 | 531 | iter_opts.flags = pathspec_match_iter_flags(flags); |
e91f9a8f | 532 | |
ed1c6446 | 533 | if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) { |
d2ce27dd | 534 | error = pathspec_match_from_iterator(out, iter, flags, ps); |
d2ce27dd RB |
535 | git_iterator_free(iter); |
536 | } | |
e91f9a8f RB |
537 | |
538 | return error; | |
539 | } | |
540 | ||
d2ce27dd RB |
541 | int git_pathspec_match_index( |
542 | git_pathspec_match_list **out, | |
543 | git_index *index, | |
544 | uint32_t flags, | |
545 | git_pathspec *ps) | |
546 | { | |
d2ce27dd | 547 | git_iterator *iter; |
ed1c6446 ET |
548 | git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; |
549 | int error = 0; | |
d2ce27dd | 550 | |
c25aa7cd | 551 | GIT_ASSERT_ARG(index); |
d2ce27dd | 552 | |
ed1c6446 | 553 | iter_opts.flags = pathspec_match_iter_flags(flags); |
d2ce27dd | 554 | |
3679ebae | 555 | if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) { |
d2ce27dd | 556 | error = pathspec_match_from_iterator(out, iter, flags, ps); |
d2ce27dd RB |
557 | git_iterator_free(iter); |
558 | } | |
559 | ||
560 | return error; | |
561 | } | |
562 | ||
563 | int git_pathspec_match_tree( | |
564 | git_pathspec_match_list **out, | |
565 | git_tree *tree, | |
566 | uint32_t flags, | |
567 | git_pathspec *ps) | |
568 | { | |
d2ce27dd | 569 | git_iterator *iter; |
ed1c6446 ET |
570 | git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; |
571 | int error = 0; | |
d2ce27dd | 572 | |
c25aa7cd | 573 | GIT_ASSERT_ARG(tree); |
d2ce27dd | 574 | |
ed1c6446 | 575 | iter_opts.flags = pathspec_match_iter_flags(flags); |
d2ce27dd | 576 | |
ed1c6446 | 577 | if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) { |
d2ce27dd | 578 | error = pathspec_match_from_iterator(out, iter, flags, ps); |
d2ce27dd RB |
579 | git_iterator_free(iter); |
580 | } | |
581 | ||
582 | return error; | |
583 | } | |
584 | ||
2b672d5b RB |
585 | int git_pathspec_match_diff( |
586 | git_pathspec_match_list **out, | |
3ff1d123 | 587 | git_diff *diff, |
2b672d5b RB |
588 | uint32_t flags, |
589 | git_pathspec *ps) | |
590 | { | |
591 | int error = 0; | |
592 | git_pathspec_match_list *m = NULL; | |
593 | struct pathspec_match_context ctxt; | |
594 | git_vector *patterns = &ps->pathspec; | |
595 | bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; | |
596 | bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; | |
597 | size_t i, pos, used_ct = 0, found_deltas = 0; | |
598 | const git_diff_delta *delta, **match; | |
599 | git_bitvec used_patterns; | |
600 | ||
c25aa7cd | 601 | GIT_ASSERT_ARG(diff); |
2b672d5b RB |
602 | |
603 | if (git_bitvec_init(&used_patterns, patterns->length) < 0) | |
604 | return -1; | |
605 | ||
606 | if (out) { | |
607 | *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); | |
ac3d33df | 608 | GIT_ERROR_CHECK_ALLOC(m); |
2b672d5b RB |
609 | } |
610 | ||
611 | pathspec_match_context_init( | |
612 | &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, | |
613 | git_diff_is_sorted_icase(diff)); | |
614 | ||
615 | git_vector_foreach(&diff->deltas, i, delta) { | |
616 | /* search for match with delta */ | |
617 | int result = git_pathspec__match_at( | |
618 | &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); | |
619 | ||
620 | /* no matches for this path */ | |
621 | if (result < 0) | |
622 | continue; | |
623 | ||
624 | /* mark the matched pattern as used */ | |
625 | used_ct += pathspec_mark_pattern(&used_patterns, pos); | |
626 | ||
627 | /* if result was a negative pattern match, then don't list file */ | |
628 | if (!result) | |
629 | continue; | |
630 | ||
631 | ++found_deltas; | |
632 | ||
633 | /* if find_failures is on, check if any later patterns also match */ | |
634 | if (find_failures && used_ct < patterns->length) | |
635 | used_ct += pathspec_mark_remaining( | |
636 | &used_patterns, patterns, &ctxt, pos + 1, | |
637 | delta->old_file.path, delta->new_file.path); | |
638 | ||
639 | /* if only looking at failures, exit early or just continue */ | |
640 | if (failures_only || !out) { | |
641 | if (used_ct == patterns->length) | |
642 | break; | |
643 | continue; | |
644 | } | |
645 | ||
646 | /* insert matched delta into matches array */ | |
647 | if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { | |
648 | error = -1; | |
649 | goto done; | |
650 | } else { | |
651 | *match = delta; | |
652 | } | |
653 | } | |
654 | ||
655 | /* insert patterns that had no matches into failures array */ | |
656 | if (find_failures && used_ct < patterns->length && | |
657 | (error = pathspec_build_failure_array( | |
658 | &m->failures, patterns, &used_patterns, &m->pool)) < 0) | |
659 | goto done; | |
660 | ||
661 | /* if every pattern failed to match, then we have failed */ | |
662 | if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { | |
ac3d33df | 663 | git_error_set(GIT_ERROR_INVALID, "no matching deltas were found"); |
2b672d5b RB |
664 | error = GIT_ENOTFOUND; |
665 | } | |
666 | ||
667 | done: | |
668 | git_bitvec_free(&used_patterns); | |
669 | ||
670 | if (error < 0) { | |
671 | pathspec_match_free(m); | |
672 | if (out) *out = NULL; | |
673 | } | |
674 | ||
675 | return error; | |
676 | } | |
677 | ||
d2ce27dd RB |
678 | void git_pathspec_match_list_free(git_pathspec_match_list *m) |
679 | { | |
2b672d5b RB |
680 | if (m) |
681 | pathspec_match_free(m); | |
d2ce27dd RB |
682 | } |
683 | ||
684 | size_t git_pathspec_match_list_entrycount( | |
685 | const git_pathspec_match_list *m) | |
686 | { | |
2b672d5b | 687 | return m ? git_array_size(m->matches) : 0; |
d2ce27dd RB |
688 | } |
689 | ||
690 | const char *git_pathspec_match_list_entry( | |
691 | const git_pathspec_match_list *m, size_t pos) | |
692 | { | |
2b672d5b RB |
693 | if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || |
694 | !git_array_valid_index(m->matches, pos)) | |
695 | return NULL; | |
696 | ||
697 | return *((const char **)git_array_get(m->matches, pos)); | |
698 | } | |
699 | ||
700 | const git_diff_delta *git_pathspec_match_list_diff_entry( | |
701 | const git_pathspec_match_list *m, size_t pos) | |
702 | { | |
703 | if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || | |
704 | !git_array_valid_index(m->matches, pos)) | |
705 | return NULL; | |
706 | ||
707 | return *((const git_diff_delta **)git_array_get(m->matches, pos)); | |
d2ce27dd RB |
708 | } |
709 | ||
710 | size_t git_pathspec_match_list_failed_entrycount( | |
711 | const git_pathspec_match_list *m) | |
712 | { | |
2b672d5b | 713 | return m ? git_array_size(m->failures) : 0; |
d2ce27dd RB |
714 | } |
715 | ||
716 | const char * git_pathspec_match_list_failed_entry( | |
717 | const git_pathspec_match_list *m, size_t pos) | |
e91f9a8f | 718 | { |
2b672d5b RB |
719 | char **entry = m ? git_array_get(m->failures, pos) : NULL; |
720 | ||
d2ce27dd | 721 | return entry ? *entry : NULL; |
e91f9a8f | 722 | } |