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