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