2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
10 #include "git2/pathspec.h"
11 #include "git2/diff.h"
13 #include "attr_file.h"
15 #include "repository.h"
19 #include "wildmatch.h"
21 /* what is the common non-wildcard prefix for all items in the pathspec */
22 char *git_pathspec_prefix(const git_strarray
*pathspec
)
24 git_buf prefix
= GIT_BUF_INIT
;
27 if (!pathspec
|| !pathspec
->count
||
28 git_buf_text_common_prefix(&prefix
, pathspec
) < 0)
31 /* diff prefix will only be leading non-wildcards */
32 for (scan
= prefix
.ptr
; *scan
; ++scan
) {
33 if (git__iswildcard(*scan
) &&
34 (scan
== prefix
.ptr
|| (*(scan
- 1) != '\\')))
37 git_buf_truncate(&prefix
, scan
- prefix
.ptr
);
39 if (prefix
.size
<= 0) {
40 git_buf_dispose(&prefix
);
44 git_buf_text_unescape(&prefix
);
46 return git_buf_detach(&prefix
);
49 /* is there anything in the spec that needs to be filtered on */
50 bool git_pathspec_is_empty(const git_strarray
*pathspec
)
57 for (i
= 0; i
< pathspec
->count
; ++i
) {
58 const char *str
= pathspec
->strings
[i
];
67 /* build a vector of fnmatch patterns to evaluate efficiently */
68 int git_pathspec__vinit(
69 git_vector
*vspec
, const git_strarray
*strspec
, git_pool
*strpool
)
73 memset(vspec
, 0, sizeof(*vspec
));
75 if (git_pathspec_is_empty(strspec
))
78 if (git_vector_init(vspec
, strspec
->count
, NULL
) < 0)
81 for (i
= 0; i
< strspec
->count
; ++i
) {
83 const char *pattern
= strspec
->strings
[i
];
84 git_attr_fnmatch
*match
= git__calloc(1, sizeof(git_attr_fnmatch
));
88 match
->flags
= GIT_ATTR_FNMATCH_ALLOWSPACE
| GIT_ATTR_FNMATCH_ALLOWNEG
;
90 ret
= git_attr_fnmatch__parse(match
, strpool
, NULL
, &pattern
);
91 if (ret
== GIT_ENOTFOUND
) {
99 if (git_vector_insert(vspec
, match
) < 0)
106 /* free data from the pathspec vector */
107 void git_pathspec__vfree(git_vector
*vspec
)
109 git_vector_free_deep(vspec
);
112 struct pathspec_match_context
{
114 int (*strcomp
)(const char *, const char *);
115 int (*strncomp
)(const char *, const char *, size_t);
118 static void pathspec_match_context_init(
119 struct pathspec_match_context
*ctxt
,
120 bool disable_fnmatch
,
124 ctxt
->wildmatch_flags
= -1;
126 ctxt
->wildmatch_flags
= WM_CASEFOLD
;
128 ctxt
->wildmatch_flags
= 0;
131 ctxt
->strcomp
= git__strcasecmp
;
132 ctxt
->strncomp
= git__strncasecmp
;
134 ctxt
->strcomp
= git__strcmp
;
135 ctxt
->strncomp
= git__strncmp
;
139 static int pathspec_match_one(
140 const git_attr_fnmatch
*match
,
141 struct pathspec_match_context
*ctxt
,
144 int result
= (match
->flags
& GIT_ATTR_FNMATCH_MATCH_ALL
) ? 0 : WM_NOMATCH
;
146 if (result
== WM_NOMATCH
)
147 result
= ctxt
->strcomp(match
->pattern
, path
) ? WM_NOMATCH
: 0;
149 if (ctxt
->wildmatch_flags
>= 0 && result
== WM_NOMATCH
)
150 result
= wildmatch(match
->pattern
, path
, ctxt
->wildmatch_flags
);
152 /* if we didn't match, look for exact dirname prefix match */
153 if (result
== WM_NOMATCH
&&
154 (match
->flags
& GIT_ATTR_FNMATCH_HASWILD
) == 0 &&
155 ctxt
->strncomp(path
, match
->pattern
, match
->length
) == 0 &&
156 path
[match
->length
] == '/')
159 /* if we didn't match and this is a negative match, check for exact
160 * match of filename with leading '!'
162 if (result
== WM_NOMATCH
&&
163 (match
->flags
& GIT_ATTR_FNMATCH_NEGATIVE
) != 0 &&
165 ctxt
->strncomp(path
+ 1, match
->pattern
, match
->length
) == 0 &&
166 (!path
[match
->length
+ 1] || path
[match
->length
+ 1] == '/'))
170 return (match
->flags
& GIT_ATTR_FNMATCH_NEGATIVE
) ? 0 : 1;
174 static int git_pathspec__match_at(
176 const git_vector
*vspec
,
177 struct pathspec_match_context
*ctxt
,
181 int result
= GIT_ENOTFOUND
;
183 const git_attr_fnmatch
*match
;
185 git_vector_foreach(vspec
, i
, match
) {
186 if (path0
&& (result
= pathspec_match_one(match
, ctxt
, path0
)) >= 0)
188 if (path1
&& (result
= pathspec_match_one(match
, ctxt
, path1
)) >= 0)
196 /* match a path against the vectorized pathspec */
197 bool git_pathspec__match(
198 const git_vector
*vspec
,
200 bool disable_fnmatch
,
202 const char **matched_pathspec
,
207 struct pathspec_match_context ctxt
;
209 if (matched_pathspec
)
210 *matched_pathspec
= NULL
;
212 *matched_at
= GIT_PATHSPEC_NOMATCH
;
214 if (!vspec
|| !vspec
->length
)
217 pathspec_match_context_init(&ctxt
, disable_fnmatch
, casefold
);
219 result
= git_pathspec__match_at(&pos
, vspec
, &ctxt
, path
, NULL
);
221 if (matched_pathspec
) {
222 const git_attr_fnmatch
*match
= git_vector_get(vspec
, pos
);
223 *matched_pathspec
= match
->pattern
;
234 int git_pathspec__init(git_pathspec
*ps
, const git_strarray
*paths
)
238 memset(ps
, 0, sizeof(*ps
));
240 ps
->prefix
= git_pathspec_prefix(paths
);
241 git_pool_init(&ps
->pool
, 1);
243 if ((error
= git_pathspec__vinit(&ps
->pathspec
, paths
, &ps
->pool
)) < 0)
244 git_pathspec__clear(ps
);
249 void git_pathspec__clear(git_pathspec
*ps
)
251 git__free(ps
->prefix
);
252 git_pathspec__vfree(&ps
->pathspec
);
253 git_pool_clear(&ps
->pool
);
254 memset(ps
, 0, sizeof(*ps
));
257 int git_pathspec_new(git_pathspec
**out
, const git_strarray
*pathspec
)
260 git_pathspec
*ps
= git__malloc(sizeof(git_pathspec
));
261 GIT_ERROR_CHECK_ALLOC(ps
);
263 if ((error
= git_pathspec__init(ps
, pathspec
)) < 0) {
268 GIT_REFCOUNT_INC(ps
);
273 static void pathspec_free(git_pathspec
*ps
)
275 git_pathspec__clear(ps
);
279 void git_pathspec_free(git_pathspec
*ps
)
283 GIT_REFCOUNT_DEC(ps
, pathspec_free
);
286 int git_pathspec_matches_path(
287 const git_pathspec
*ps
, uint32_t flags
, const char *path
)
289 bool no_fnmatch
= (flags
& GIT_PATHSPEC_NO_GLOB
) != 0;
290 bool casefold
= (flags
& GIT_PATHSPEC_IGNORE_CASE
) != 0;
294 return (0 != git_pathspec__match(
295 &ps
->pathspec
, path
, no_fnmatch
, casefold
, NULL
, NULL
));
298 static void pathspec_match_free(git_pathspec_match_list
*m
)
303 git_pathspec_free(m
->pathspec
);
306 git_array_clear(m
->matches
);
307 git_array_clear(m
->failures
);
308 git_pool_clear(&m
->pool
);
312 static git_pathspec_match_list
*pathspec_match_alloc(
313 git_pathspec
*ps
, int datatype
)
315 git_pathspec_match_list
*m
= git__calloc(1, sizeof(git_pathspec_match_list
));
319 git_pool_init(&m
->pool
, 1);
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
325 GIT_REFCOUNT_INC(ps
);
327 m
->datatype
= datatype
;
332 GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec
*used
, size_t pos
)
334 if (!git_bitvec_get(used
, pos
)) {
335 git_bitvec_set(used
, pos
, true);
342 static size_t pathspec_mark_remaining(
344 git_vector
*patterns
,
345 struct pathspec_match_context
*ctxt
,
355 for (; start
< patterns
->length
; ++start
) {
356 const git_attr_fnmatch
*pat
= git_vector_get(patterns
, start
);
358 if (git_bitvec_get(used
, start
))
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
);
370 static int pathspec_build_failure_array(
371 git_pathspec_string_array_t
*failures
,
372 git_vector
*patterns
,
378 const git_attr_fnmatch
*pat
;
380 for (pos
= 0; pos
< patterns
->length
; ++pos
) {
381 if (git_bitvec_get(used
, pos
))
384 if ((failed
= git_array_alloc(*failures
)) == NULL
)
387 pat
= git_vector_get(patterns
, pos
);
389 if ((*failed
= git_pool_strdup(pool
, pat
->pattern
)) == NULL
)
396 static int pathspec_match_from_iterator(
397 git_pathspec_match_list
**out
,
403 git_pathspec_match_list
*m
= NULL
;
404 const git_index_entry
*entry
= NULL
;
405 struct pathspec_match_context ctxt
;
406 git_vector
*patterns
= &ps
->pathspec
;
407 bool find_failures
= out
&& (flags
& GIT_PATHSPEC_FIND_FAILURES
) != 0;
408 bool failures_only
= !out
|| (flags
& GIT_PATHSPEC_FAILURES_ONLY
) != 0;
409 size_t pos
, used_ct
= 0, found_files
= 0;
410 git_index
*index
= NULL
;
411 git_bitvec used_patterns
;
414 if (git_bitvec_init(&used_patterns
, patterns
->length
) < 0)
418 *out
= m
= pathspec_match_alloc(ps
, PATHSPEC_DATATYPE_STRINGS
);
419 GIT_ERROR_CHECK_ALLOC(m
);
422 if ((error
= git_iterator_reset_range(iter
, ps
->prefix
, ps
->prefix
)) < 0)
425 if (git_iterator_type(iter
) == GIT_ITERATOR_WORKDIR
&&
426 (error
= git_repository_index__weakptr(
427 &index
, git_iterator_owner(iter
))) < 0)
430 pathspec_match_context_init(
431 &ctxt
, (flags
& GIT_PATHSPEC_NO_GLOB
) != 0,
432 git_iterator_ignore_case(iter
));
434 while (!(error
= git_iterator_advance(&entry
, iter
))) {
435 /* search for match with entry->path */
436 int result
= git_pathspec__match_at(
437 &pos
, patterns
, &ctxt
, entry
->path
, NULL
);
439 /* no matches for this path */
443 /* if result was a negative pattern match, then don't list file */
445 used_ct
+= pathspec_mark_pattern(&used_patterns
, pos
);
449 /* check if path is ignored and untracked */
451 git_iterator_current_is_ignored(iter
) &&
452 git_index__find_pos(NULL
, index
, entry
->path
, 0, GIT_INDEX_STAGE_ANY
) < 0)
455 /* mark the matched pattern as used */
456 used_ct
+= pathspec_mark_pattern(&used_patterns
, pos
);
459 /* if find_failures is on, check if any later patterns also match */
460 if (find_failures
&& used_ct
< patterns
->length
)
461 used_ct
+= pathspec_mark_remaining(
462 &used_patterns
, patterns
, &ctxt
, pos
+ 1, entry
->path
, NULL
);
464 /* if only looking at failures, exit early or just continue */
465 if (failures_only
|| !out
) {
466 if (used_ct
== patterns
->length
)
471 /* insert matched path into matches array */
472 if ((file
= (char **)git_array_alloc(m
->matches
)) == NULL
||
473 (*file
= git_pool_strdup(&m
->pool
, entry
->path
)) == NULL
) {
479 if (error
< 0 && error
!= GIT_ITEROVER
)
483 /* insert patterns that had no matches into failures array */
484 if (find_failures
&& used_ct
< patterns
->length
&&
485 (error
= pathspec_build_failure_array(
486 &m
->failures
, patterns
, &used_patterns
, &m
->pool
)) < 0)
489 /* if every pattern failed to match, then we have failed */
490 if ((flags
& GIT_PATHSPEC_NO_MATCH_ERROR
) != 0 && !found_files
) {
491 git_error_set(GIT_ERROR_INVALID
, "no matching files were found");
492 error
= GIT_ENOTFOUND
;
496 git_bitvec_free(&used_patterns
);
499 pathspec_match_free(m
);
500 if (out
) *out
= NULL
;
506 static git_iterator_flag_t
pathspec_match_iter_flags(uint32_t flags
)
508 git_iterator_flag_t f
= 0;
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
;
518 int git_pathspec_match_workdir(
519 git_pathspec_match_list
**out
,
520 git_repository
*repo
,
525 git_iterator_options iter_opts
= GIT_ITERATOR_OPTIONS_INIT
;
530 iter_opts
.flags
= pathspec_match_iter_flags(flags
);
532 if (!(error
= git_iterator_for_workdir(&iter
, repo
, NULL
, NULL
, &iter_opts
))) {
533 error
= pathspec_match_from_iterator(out
, iter
, flags
, ps
);
534 git_iterator_free(iter
);
540 int git_pathspec_match_index(
541 git_pathspec_match_list
**out
,
547 git_iterator_options iter_opts
= GIT_ITERATOR_OPTIONS_INIT
;
552 iter_opts
.flags
= pathspec_match_iter_flags(flags
);
554 if (!(error
= git_iterator_for_index(&iter
, git_index_owner(index
), index
, &iter_opts
))) {
555 error
= pathspec_match_from_iterator(out
, iter
, flags
, ps
);
556 git_iterator_free(iter
);
562 int git_pathspec_match_tree(
563 git_pathspec_match_list
**out
,
569 git_iterator_options iter_opts
= GIT_ITERATOR_OPTIONS_INIT
;
574 iter_opts
.flags
= pathspec_match_iter_flags(flags
);
576 if (!(error
= git_iterator_for_tree(&iter
, tree
, &iter_opts
))) {
577 error
= pathspec_match_from_iterator(out
, iter
, flags
, ps
);
578 git_iterator_free(iter
);
584 int git_pathspec_match_diff(
585 git_pathspec_match_list
**out
,
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
;
602 if (git_bitvec_init(&used_patterns
, patterns
->length
) < 0)
606 *out
= m
= pathspec_match_alloc(ps
, PATHSPEC_DATATYPE_DIFF
);
607 GIT_ERROR_CHECK_ALLOC(m
);
610 pathspec_match_context_init(
611 &ctxt
, (flags
& GIT_PATHSPEC_NO_GLOB
) != 0,
612 git_diff_is_sorted_icase(diff
));
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
);
619 /* no matches for this path */
623 /* mark the matched pattern as used */
624 used_ct
+= pathspec_mark_pattern(&used_patterns
, pos
);
626 /* if result was a negative pattern match, then don't list file */
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
);
638 /* if only looking at failures, exit early or just continue */
639 if (failures_only
|| !out
) {
640 if (used_ct
== patterns
->length
)
645 /* insert matched delta into matches array */
646 if (!(match
= (const git_diff_delta
**)git_array_alloc(m
->matches
))) {
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)
660 /* if every pattern failed to match, then we have failed */
661 if ((flags
& GIT_PATHSPEC_NO_MATCH_ERROR
) != 0 && !found_deltas
) {
662 git_error_set(GIT_ERROR_INVALID
, "no matching deltas were found");
663 error
= GIT_ENOTFOUND
;
667 git_bitvec_free(&used_patterns
);
670 pathspec_match_free(m
);
671 if (out
) *out
= NULL
;
677 void git_pathspec_match_list_free(git_pathspec_match_list
*m
)
680 pathspec_match_free(m
);
683 size_t git_pathspec_match_list_entrycount(
684 const git_pathspec_match_list
*m
)
686 return m
? git_array_size(m
->matches
) : 0;
689 const char *git_pathspec_match_list_entry(
690 const git_pathspec_match_list
*m
, size_t pos
)
692 if (!m
|| m
->datatype
!= PATHSPEC_DATATYPE_STRINGS
||
693 !git_array_valid_index(m
->matches
, pos
))
696 return *((const char **)git_array_get(m
->matches
, pos
));
699 const git_diff_delta
*git_pathspec_match_list_diff_entry(
700 const git_pathspec_match_list
*m
, size_t pos
)
702 if (!m
|| m
->datatype
!= PATHSPEC_DATATYPE_DIFF
||
703 !git_array_valid_index(m
->matches
, pos
))
706 return *((const git_diff_delta
**)git_array_get(m
->matches
, pos
));
709 size_t git_pathspec_match_list_failed_entrycount(
710 const git_pathspec_match_list
*m
)
712 return m
? git_array_size(m
->failures
) : 0;
715 const char * git_pathspec_match_list_failed_entry(
716 const git_pathspec_match_list
*m
, size_t pos
)
718 char **entry
= m
? git_array_get(m
->failures
, pos
) : NULL
;
720 return entry
? *entry
: NULL
;