1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
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 
8 #include "pathspec.h"
9 
10 #include "git2/pathspec.h"
11 #include "git2/diff.h"
12 #include "attr_file.h"
13 #include "iterator.h"
14 #include "repository.h"
15 #include "index.h"
16 #include "bitvec.h"
17 #include "diff.h"
18 #include "wildmatch.h"
19 
20 /* what is the common non-wildcard prefix for all items in the pathspec */
git_pathspec_prefix(const git_strarray * 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 ||
27 		git_buf_common_prefix(&prefix, pathspec) < 0)
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) {
39 		git_buf_dispose(&prefix);
40 		return NULL;
41 	}
42 
43 	git_buf_unescape(&prefix);
44 
45 	return git_buf_detach(&prefix);
46 }
47 
48 /* is there anything in the spec that needs to be filtered on */
git_pathspec_is_empty(const git_strarray * pathspec)49 bool git_pathspec_is_empty(const git_strarray *pathspec)
50 {
51 	size_t i;
52 
53 	if (pathspec == NULL)
54 		return true;
55 
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 
63 	return true;
64 }
65 
66 /* build a vector of fnmatch patterns to evaluate efficiently */
git_pathspec__vinit(git_vector * vspec,const git_strarray * strspec,git_pool * strpool)67 int git_pathspec__vinit(
68 	git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
69 {
70 	size_t i;
71 
72 	memset(vspec, 0, sizeof(*vspec));
73 
74 	if (git_pathspec_is_empty(strspec))
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 
87 		match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
88 
89 		ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
90 		if (ret == GIT_ENOTFOUND) {
91 			git__free(match);
92 			continue;
93 		} else if (ret < 0) {
94 			git__free(match);
95 			return ret;
96 		}
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 */
git_pathspec__vfree(git_vector * vspec)106 void git_pathspec__vfree(git_vector *vspec)
107 {
108 	git_vector_free_deep(vspec);
109 }
110 
111 struct pathspec_match_context {
112 	int wildmatch_flags;
113 	int (*strcomp)(const char *, const char *);
114 	int (*strncomp)(const char *, const char *, size_t);
115 };
116 
pathspec_match_context_init(struct pathspec_match_context * ctxt,bool disable_fnmatch,bool casefold)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)
123 		ctxt->wildmatch_flags = -1;
124 	else if (casefold)
125 		ctxt->wildmatch_flags = WM_CASEFOLD;
126 	else
127 		ctxt->wildmatch_flags = 0;
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 
pathspec_match_one(const git_attr_fnmatch * match,struct pathspec_match_context * ctxt,const char * path)138 static int pathspec_match_one(
139 	const git_attr_fnmatch *match,
140 	struct pathspec_match_context *ctxt,
141 	const char *path)
142 {
143 	int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH;
144 
145 	if (result == WM_NOMATCH)
146 		result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0;
147 
148 	if (ctxt->wildmatch_flags >= 0 && result == WM_NOMATCH)
149 		result = wildmatch(match->pattern, path, ctxt->wildmatch_flags);
150 
151 	/* if we didn't match, look for exact dirname prefix match */
152 	if (result == WM_NOMATCH &&
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 
158 	/* if we didn't match and this is a negative match, check for exact
159 	 * match of filename with leading '!'
160 	 */
161 	if (result == WM_NOMATCH &&
162 		(match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 &&
163 		*path == '!' &&
164 		ctxt->strncomp(path + 1, match->pattern, match->length) == 0 &&
165 		(!path[match->length + 1] || path[match->length + 1] == '/'))
166 		return 1;
167 
168 	if (result == 0)
169 		return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1;
170 	return -1;
171 }
172 
git_pathspec__match_at(size_t * matched_at,const git_vector * vspec,struct pathspec_match_context * ctxt,const char * path0,const char * path1)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 
195 /* match a path against the vectorized pathspec */
git_pathspec__match(const git_vector * vspec,const char * path,bool disable_fnmatch,bool casefold,const char ** matched_pathspec,size_t * matched_at)196 bool git_pathspec__match(
197 	const git_vector *vspec,
198 	const char *path,
199 	bool disable_fnmatch,
200 	bool casefold,
201 	const char **matched_pathspec,
202 	size_t *matched_at)
203 {
204 	int result;
205 	size_t pos;
206 	struct pathspec_match_context ctxt;
207 
208 	if (matched_pathspec)
209 		*matched_pathspec = NULL;
210 	if (matched_at)
211 		*matched_at = GIT_PATHSPEC_NOMATCH;
212 
213 	if (!vspec || !vspec->length)
214 		return true;
215 
216 	pathspec_match_context_init(&ctxt, disable_fnmatch, casefold);
217 
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;
223 		}
224 
225 		if (matched_at)
226 			*matched_at = pos;
227 	}
228 
229 	return (result > 0);
230 }
231 
232 
git_pathspec__init(git_pathspec * ps,const git_strarray * paths)233 int git_pathspec__init(git_pathspec *ps, const git_strarray *paths)
234 {
235 	int error = 0;
236 
237 	memset(ps, 0, sizeof(*ps));
238 
239 	ps->prefix = git_pathspec_prefix(paths);
240 
241 	if ((error = git_pool_init(&ps->pool, 1)) < 0 ||
242 	    (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0)
243 		git_pathspec__clear(ps);
244 
245 	return error;
246 }
247 
git_pathspec__clear(git_pathspec * ps)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 
git_pathspec_new(git_pathspec ** out,const git_strarray * pathspec)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));
260 	GIT_ERROR_CHECK_ALLOC(ps);
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 
pathspec_free(git_pathspec * ps)272 static void pathspec_free(git_pathspec *ps)
273 {
274 	git_pathspec__clear(ps);
275 	git__free(ps);
276 }
277 
git_pathspec_free(git_pathspec * ps)278 void git_pathspec_free(git_pathspec *ps)
279 {
280 	if (!ps)
281 		return;
282 	GIT_REFCOUNT_DEC(ps, pathspec_free);
283 }
284 
git_pathspec_matches_path(const git_pathspec * ps,uint32_t flags,const char * path)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 
291 	GIT_ASSERT_ARG(ps);
292 	GIT_ASSERT_ARG(path);
293 
294 	return (0 != git_pathspec__match(
295 		&ps->pathspec, path, no_fnmatch, casefold, NULL, NULL));
296 }
297 
pathspec_match_free(git_pathspec_match_list * m)298 static void pathspec_match_free(git_pathspec_match_list *m)
299 {
300 	if (!m)
301 		return;
302 
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 
pathspec_match_alloc(git_pathspec * ps,int datatype)312 static git_pathspec_match_list *pathspec_match_alloc(
313 	git_pathspec *ps, int datatype)
314 {
315 	git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list));
316 	if (!m)
317 		return NULL;
318 
319 	if (git_pool_init(&m->pool, 1) < 0)
320 		return NULL;
321 
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;
328 	m->datatype = datatype;
329 
330 	return m;
331 }
332 
pathspec_mark_pattern(git_bitvec * used,size_t pos)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 
pathspec_mark_remaining(git_bitvec * used,git_vector * patterns,struct pathspec_match_context * ctxt,size_t start,const char * path0,const char * path1)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 
pathspec_build_failure_array(git_pathspec_string_array_t * failures,git_vector * patterns,git_bitvec * used,git_pool * pool)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)
376 {
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;
392 	}
393 
394 	return 0;
395 }
396 
pathspec_match_from_iterator(git_pathspec_match_list ** out,git_iterator * iter,uint32_t flags,git_pathspec * ps)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;
404 	git_pathspec_match_list *m = NULL;
405 	const git_index_entry *entry = NULL;
406 	struct pathspec_match_context ctxt;
407 	git_vector *patterns = &ps->pathspec;
408 	bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
409 	bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
410 	size_t pos, used_ct = 0, found_files = 0;
411 	git_index *index = NULL;
412 	git_bitvec used_patterns;
413 	char **file;
414 
415 	if (git_bitvec_init(&used_patterns, patterns->length) < 0)
416 		return -1;
417 
418 	if (out) {
419 		*out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS);
420 		GIT_ERROR_CHECK_ALLOC(m);
421 	}
422 
423 	if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0)
424 		goto done;
425 
426 	if (git_iterator_type(iter) == GIT_ITERATOR_WORKDIR &&
427 		(error = git_repository_index__weakptr(
428 			&index, git_iterator_owner(iter))) < 0)
429 		goto done;
430 
431 	pathspec_match_context_init(
432 		&ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
433 		git_iterator_ignore_case(iter));
434 
435 	while (!(error = git_iterator_advance(&entry, iter))) {
436 		/* search for match with entry->path */
437 		int result = git_pathspec__match_at(
438 			&pos, patterns, &ctxt, entry->path, NULL);
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) {
446 			used_ct += pathspec_mark_pattern(&used_patterns, pos);
447 			continue;
448 		}
449 
450 		/* check if path is ignored and untracked */
451 		if (index != NULL &&
452 			git_iterator_current_is_ignored(iter) &&
453 			git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0)
454 			continue;
455 
456 		/* mark the matched pattern as used */
457 		used_ct += pathspec_mark_pattern(&used_patterns, pos);
458 		++found_files;
459 
460 		/* if find_failures is on, check if any later patterns also match */
461 		if (find_failures && used_ct < patterns->length)
462 			used_ct += pathspec_mark_remaining(
463 				&used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL);
464 
465 		/* if only looking at failures, exit early or just continue */
466 		if (failures_only || !out) {
467 			if (used_ct == patterns->length)
468 				break;
469 			continue;
470 		}
471 
472 		/* insert matched path into matches array */
473 		if ((file = (char **)git_array_alloc(m->matches)) == NULL ||
474 			(*file = git_pool_strdup(&m->pool, entry->path)) == NULL) {
475 			error = -1;
476 			goto done;
477 		}
478 	}
479 
480 	if (error < 0 && error != GIT_ITEROVER)
481 		goto done;
482 	error = 0;
483 
484 	/* insert patterns that had no matches into failures array */
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;
489 
490 	/* if every pattern failed to match, then we have failed */
491 	if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) {
492 		git_error_set(GIT_ERROR_INVALID, "no matching files were found");
493 		error = GIT_ENOTFOUND;
494 	}
495 
496 done:
497 	git_bitvec_free(&used_patterns);
498 
499 	if (error < 0) {
500 		pathspec_match_free(m);
501 		if (out) *out = NULL;
502 	}
503 
504 	return error;
505 }
506 
pathspec_match_iter_flags(uint32_t flags)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;
515 
516 	return f;
517 }
518 
git_pathspec_match_workdir(git_pathspec_match_list ** out,git_repository * repo,uint32_t flags,git_pathspec * ps)519 int git_pathspec_match_workdir(
520 	git_pathspec_match_list **out,
521 	git_repository *repo,
522 	uint32_t flags,
523 	git_pathspec *ps)
524 {
525 	git_iterator *iter;
526 	git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
527 	int error = 0;
528 
529 	GIT_ASSERT_ARG(repo);
530 
531 	iter_opts.flags = pathspec_match_iter_flags(flags);
532 
533 	if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) {
534 		error = pathspec_match_from_iterator(out, iter, flags, ps);
535 		git_iterator_free(iter);
536 	}
537 
538 	return error;
539 }
540 
git_pathspec_match_index(git_pathspec_match_list ** out,git_index * index,uint32_t flags,git_pathspec * ps)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 {
547 	git_iterator *iter;
548 	git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
549 	int error = 0;
550 
551 	GIT_ASSERT_ARG(index);
552 
553 	iter_opts.flags = pathspec_match_iter_flags(flags);
554 
555 	if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) {
556 		error = pathspec_match_from_iterator(out, iter, flags, ps);
557 		git_iterator_free(iter);
558 	}
559 
560 	return error;
561 }
562 
git_pathspec_match_tree(git_pathspec_match_list ** out,git_tree * tree,uint32_t flags,git_pathspec * ps)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 {
569 	git_iterator *iter;
570 	git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
571 	int error = 0;
572 
573 	GIT_ASSERT_ARG(tree);
574 
575 	iter_opts.flags = pathspec_match_iter_flags(flags);
576 
577 	if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) {
578 		error = pathspec_match_from_iterator(out, iter, flags, ps);
579 		git_iterator_free(iter);
580 	}
581 
582 	return error;
583 }
584 
git_pathspec_match_diff(git_pathspec_match_list ** out,git_diff * diff,uint32_t flags,git_pathspec * ps)585 int git_pathspec_match_diff(
586 	git_pathspec_match_list **out,
587 	git_diff *diff,
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 
601 	GIT_ASSERT_ARG(diff);
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);
608 		GIT_ERROR_CHECK_ALLOC(m);
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) {
663 		git_error_set(GIT_ERROR_INVALID, "no matching deltas were found");
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 
git_pathspec_match_list_free(git_pathspec_match_list * m)678 void git_pathspec_match_list_free(git_pathspec_match_list *m)
679 {
680 	if (m)
681 		pathspec_match_free(m);
682 }
683 
git_pathspec_match_list_entrycount(const git_pathspec_match_list * m)684 size_t git_pathspec_match_list_entrycount(
685 	const git_pathspec_match_list *m)
686 {
687 	return m ? git_array_size(m->matches) : 0;
688 }
689 
git_pathspec_match_list_entry(const git_pathspec_match_list * m,size_t pos)690 const char *git_pathspec_match_list_entry(
691 	const git_pathspec_match_list *m, size_t pos)
692 {
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 
git_pathspec_match_list_diff_entry(const git_pathspec_match_list * m,size_t pos)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));
708 }
709 
git_pathspec_match_list_failed_entrycount(const git_pathspec_match_list * m)710 size_t git_pathspec_match_list_failed_entrycount(
711 	const git_pathspec_match_list *m)
712 {
713 	return m ? git_array_size(m->failures) : 0;
714 }
715 
git_pathspec_match_list_failed_entry(const git_pathspec_match_list * m,size_t pos)716 const char * git_pathspec_match_list_failed_entry(
717 	const git_pathspec_match_list *m, size_t pos)
718 {
719 	char **entry = m ? git_array_get(m->failures, pos) : NULL;
720 
721 	return entry ? *entry : NULL;
722 }
723