]> git.proxmox.com Git - libgit2.git/blame - src/pathspec.c
Basic bit vector
[libgit2.git] / src / pathspec.c
CommitLineData
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"
2e3d4b96 9#include "pathspec.h"
7bf87ab6 10#include "buf_text.h"
2e3d4b96 11#include "attr_file.h"
d2ce27dd
RB
12#include "iterator.h"
13#include "repository.h"
14#include "index.h"
2e3d4b96
RB
15
16/* what is the common non-wildcard prefix for all items in the pathspec */
17char *git_pathspec_prefix(const git_strarray *pathspec)
18{
19 git_buf prefix = GIT_BUF_INIT;
20 const char *scan;
21
22 if (!pathspec || !pathspec->count ||
7bf87ab6 23 git_buf_text_common_prefix(&prefix, pathspec) < 0)
2e3d4b96
RB
24 return NULL;
25
26 /* diff prefix will only be leading non-wildcards */
27 for (scan = prefix.ptr; *scan; ++scan) {
28 if (git__iswildcard(*scan) &&
29 (scan == prefix.ptr || (*(scan - 1) != '\\')))
30 break;
31 }
32 git_buf_truncate(&prefix, scan - prefix.ptr);
33
34 if (prefix.size <= 0) {
35 git_buf_free(&prefix);
36 return NULL;
37 }
38
7bf87ab6 39 git_buf_text_unescape(&prefix);
2e3d4b96
RB
40
41 return git_buf_detach(&prefix);
42}
43
44/* is there anything in the spec that needs to be filtered on */
0d32f39e 45bool git_pathspec_is_empty(const git_strarray *pathspec)
2e3d4b96 46{
0d32f39e 47 size_t i;
2e3d4b96 48
0d32f39e 49 if (pathspec == NULL)
2e3d4b96
RB
50 return true;
51
0d32f39e 52 for (i = 0; i < pathspec->count; ++i) {
53 const char *str = pathspec->strings[i];
54
55 if (str && str[0])
56 return false;
57 }
58
2e3d4b96
RB
59 return true;
60}
61
62/* build a vector of fnmatch patterns to evaluate efficiently */
d2ce27dd 63int git_pathspec__vinit(
2e3d4b96
RB
64 git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
65{
66 size_t i;
67
68 memset(vspec, 0, sizeof(*vspec));
69
0d32f39e 70 if (git_pathspec_is_empty(strspec))
2e3d4b96
RB
71 return 0;
72
73 if (git_vector_init(vspec, strspec->count, NULL) < 0)
74 return -1;
75
76 for (i = 0; i < strspec->count; ++i) {
77 int ret;
78 const char *pattern = strspec->strings[i];
79 git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
80 if (!match)
81 return -1;
82
83 match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
84
85 ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
86 if (ret == GIT_ENOTFOUND) {
87 git__free(match);
88 continue;
89 } else if (ret < 0)
90 return ret;
91
92 if (git_vector_insert(vspec, match) < 0)
93 return -1;
94 }
95
96 return 0;
97}
98
99/* free data from the pathspec vector */
d2ce27dd 100void git_pathspec__vfree(git_vector *vspec)
2e3d4b96
RB
101{
102 git_attr_fnmatch *match;
103 unsigned int i;
104
105 git_vector_foreach(vspec, i, match) {
106 git__free(match);
107 vspec->contents[i] = NULL;
108 }
109
110 git_vector_free(vspec);
111}
112
d2ce27dd
RB
113struct pathspec_match_context {
114 int fnmatch_flags;
115 int (*strcomp)(const char *, const char *);
116 int (*strncomp)(const char *, const char *, size_t);
117};
118
119static void pathspec_match_context_init(
120 struct pathspec_match_context *ctxt,
121 bool disable_fnmatch,
122 bool casefold)
123{
124 if (disable_fnmatch)
125 ctxt->fnmatch_flags = -1;
126 else if (casefold)
127 ctxt->fnmatch_flags = FNM_CASEFOLD;
128 else
129 ctxt->fnmatch_flags = 0;
130
131 if (casefold) {
132 ctxt->strcomp = git__strcasecmp;
133 ctxt->strncomp = git__strncasecmp;
134 } else {
135 ctxt->strcomp = git__strcmp;
136 ctxt->strncomp = git__strncmp;
137 }
138}
139
140static int pathspec_match_one(
141 const git_attr_fnmatch *match,
142 struct pathspec_match_context *ctxt,
143 const char *path)
144{
145 int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;
146
147 if (result == FNM_NOMATCH)
148 result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0;
149
150 if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH)
151 result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags);
152
153 /* if we didn't match, look for exact dirname prefix match */
154 if (result == FNM_NOMATCH &&
155 (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
156 ctxt->strncomp(path, match->pattern, match->length) == 0 &&
157 path[match->length] == '/')
158 result = 0;
159
160 if (result == 0)
161 return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1;
162 return -1;
163}
164
2e3d4b96 165/* match a path against the vectorized pathspec */
d2ce27dd
RB
166bool git_pathspec__match(
167 const git_vector *vspec,
943700ec 168 const char *path,
169 bool disable_fnmatch,
170 bool casefold,
d2ce27dd
RB
171 const char **matched_pathspec,
172 size_t *matched_at)
2e3d4b96 173{
943700ec 174 size_t i;
d2ce27dd
RB
175 const git_attr_fnmatch *match;
176 struct pathspec_match_context ctxt;
2e3d4b96 177
943700ec 178 if (matched_pathspec)
179 *matched_pathspec = NULL;
d2ce27dd
RB
180 if (matched_at)
181 *matched_at = GIT_PATHSPEC_NOMATCH;
943700ec 182
2e3d4b96
RB
183 if (!vspec || !vspec->length)
184 return true;
185
d2ce27dd 186 pathspec_match_context_init(&ctxt, disable_fnmatch, casefold);
2e3d4b96 187
d2ce27dd
RB
188 git_vector_foreach(vspec, i, match) {
189 int result = pathspec_match_one(match, &ctxt, path);
190
191 if (result >= 0) {
192 if (matched_pathspec)
193 *matched_pathspec = match->pattern;
194 if (matched_at)
195 *matched_at = i;
196
197 return (result != 0);
198 }
2e3d4b96
RB
199 }
200
d2ce27dd
RB
201 return false;
202}
1fed6b07 203
2e3d4b96 204
d2ce27dd
RB
205int git_pathspec__init(git_pathspec *ps, const git_strarray *paths)
206{
207 int error = 0;
2e3d4b96 208
d2ce27dd 209 memset(ps, 0, sizeof(*ps));
2e3d4b96 210
d2ce27dd
RB
211 ps->prefix = git_pathspec_prefix(paths);
212
213 if ((error = git_pool_init(&ps->pool, 1, 0)) < 0 ||
214 (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0)
215 git_pathspec__clear(ps);
216
217 return error;
218}
219
220void git_pathspec__clear(git_pathspec *ps)
221{
222 git__free(ps->prefix);
223 git_pathspec__vfree(&ps->pathspec);
224 git_pool_clear(&ps->pool);
225 memset(ps, 0, sizeof(*ps));
226}
227
228int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec)
229{
230 int error = 0;
231 git_pathspec *ps = git__malloc(sizeof(git_pathspec));
232 GITERR_CHECK_ALLOC(ps);
233
234 if ((error = git_pathspec__init(ps, pathspec)) < 0) {
235 git__free(ps);
236 return error;
237 }
238
239 GIT_REFCOUNT_INC(ps);
240 *out = ps;
241 return 0;
242}
243
244static void pathspec_free(git_pathspec *ps)
245{
246 git_pathspec__clear(ps);
247 git__free(ps);
248}
249
250void git_pathspec_free(git_pathspec *ps)
251{
252 if (!ps)
253 return;
254 GIT_REFCOUNT_DEC(ps, pathspec_free);
255}
943700ec 256
d2ce27dd
RB
257int git_pathspec_matches_path(
258 const git_pathspec *ps, uint32_t flags, const char *path)
259{
260 bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0;
261 bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0;
262
263 assert(ps && path);
264
265 return (0 != git_pathspec__match(
266 &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL));
267}
268
269static void pathspec_match_free(git_pathspec_match_list *m)
270{
271 git_pathspec_free(m->pathspec);
272 m->pathspec = NULL;
273
274 git_array_clear(m->matches);
275 git_array_clear(m->failures);
276 git_pool_clear(&m->pool);
277 git__free(m);
278}
279
280static git_pathspec_match_list *pathspec_match_alloc(git_pathspec *ps)
281{
282 git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list));
283
284 if (m != NULL && git_pool_init(&m->pool, 1, 0) < 0) {
285 pathspec_match_free(m);
286 m = NULL;
287 }
288
289 /* need to keep reference to pathspec and increment refcount because
290 * failures array stores pointers to the pattern strings of the
291 * pathspec that had no matches
292 */
293 GIT_REFCOUNT_INC(ps);
294 m->pathspec = ps;
295
296 return m;
297}
298
299GIT_INLINE(void) pathspec_mark_pattern(uint8_t *used, size_t pos, size_t *ct)
300{
301 if (!used[pos]) {
302 used[pos] = 1;
303 (*ct)++;
304 }
305}
306
307static int pathspec_match_from_iterator(
308 git_pathspec_match_list **out,
309 git_iterator *iter,
310 uint32_t flags,
311 git_pathspec *ps)
312{
313 int error = 0;
a8b5f116 314 git_pathspec_match_list *m = NULL;
d2ce27dd
RB
315 const git_index_entry *entry = NULL;
316 struct pathspec_match_context ctxt;
317 git_vector *patterns = &ps->pathspec;
318 bool find_failures = (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
319 bool failures_only = (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
320 size_t pos, used_ct = 0, found_files = 0;
321 git_index *index = NULL;
322 uint8_t *used_patterns = NULL;
323 char **file;
324
a8b5f116
RB
325 if (out) {
326 *out = m = pathspec_match_alloc(ps);
327 GITERR_CHECK_ALLOC(m);
328 } else {
329 failures_only = true;
330 find_failures = false;
331 }
d2ce27dd
RB
332
333 if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0)
334 goto done;
335
336 if (patterns->length > 0) {
337 used_patterns = git__calloc(patterns->length, sizeof(uint8_t));
338 GITERR_CHECK_ALLOC(used_patterns);
339 }
340
341 if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR &&
342 (error = git_repository_index__weakptr(
343 &index, git_iterator_owner(iter))) < 0)
344 goto done;
345
346 pathspec_match_context_init(&ctxt,
347 (flags & GIT_PATHSPEC_NO_GLOB) != 0, git_iterator_ignore_case(iter));
348
349 while (!(error = git_iterator_advance(&entry, iter))) {
350 int result = -1;
351
352 for (pos = 0; pos < patterns->length; ++pos) {
353 const git_attr_fnmatch *pat = git_vector_get(patterns, pos);
354
355 result = pathspec_match_one(pat, &ctxt, entry->path);
356 if (result >= 0)
357 break;
358 }
359
360 /* no matches for this path */
361 if (result < 0)
362 continue;
363
364 /* if result was a negative pattern match, then don't list file */
365 if (!result) {
366 pathspec_mark_pattern(used_patterns, pos, &used_ct);
367 continue;
368 }
369
370 /* check if path is untracked and ignored */
371 if (index != NULL &&
372 git_iterator_current_is_ignored(iter) &&
373 git_index__find(NULL, index, entry->path, GIT_INDEX_STAGE_ANY) < 0)
374 continue;
375
376 /* mark the matched pattern as used */
377 pathspec_mark_pattern(used_patterns, pos, &used_ct);
378 ++found_files;
379
380 /* if find_failures is on, check if any later patterns also match */
381 if (find_failures && used_ct < patterns->length) {
382 for (++pos; pos < patterns->length; ++pos) {
383 const git_attr_fnmatch *pat = git_vector_get(patterns, pos);
384 if (used_patterns[pos])
385 continue;
386
387 if (pathspec_match_one(pat, &ctxt, entry->path) > 0)
388 pathspec_mark_pattern(used_patterns, pos, &used_ct);
389 }
390 }
391
392 /* if only looking at failures, exit early or just continue */
a8b5f116 393 if (failures_only || !out) {
d2ce27dd
RB
394 if (used_ct == patterns->length)
395 break;
396 continue;
397 }
398
399 /* insert matched path into matches array */
400 if ((file = git_array_alloc(m->matches)) == NULL ||
401 (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) {
402 error = -1;
403 goto done;
943700ec 404 }
2e3d4b96
RB
405 }
406
d2ce27dd
RB
407 if (error < 0 && error != GIT_ITEROVER)
408 goto done;
409 error = 0;
410
411 /* insert patterns that had no matches into failures array */
412 if (find_failures && used_ct < patterns->length) {
413 for (pos = 0; pos < patterns->length; ++pos) {
414 const git_attr_fnmatch *pat = git_vector_get(patterns, pos);
415 if (used_patterns[pos])
416 continue;
417
418 if ((file = git_array_alloc(m->failures)) == NULL ||
419 (*file = git_pool_strdup(&m->pool, pat->pattern)) == NULL) {
420 error = -1;
421 goto done;
422 }
423 }
424 }
425
426 /* if every pattern failed to match, then we have failed */
427 if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) {
428 giterr_set(GITERR_INVALID, "No matching files were found");
429 error = GIT_ENOTFOUND;
430 }
431
432done:
433 git__free(used_patterns);
434
435 if (error < 0) {
436 pathspec_match_free(m);
a8b5f116 437 if (out) *out = NULL;
d2ce27dd
RB
438 }
439
440 return error;
2e3d4b96
RB
441}
442
d2ce27dd
RB
443static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags)
444{
445 git_iterator_flag_t f = 0;
446
447 if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0)
448 f |= GIT_ITERATOR_IGNORE_CASE;
449 else if ((flags & GIT_PATHSPEC_USE_CASE) != 0)
450 f |= GIT_ITERATOR_DONT_IGNORE_CASE;
e91f9a8f 451
d2ce27dd
RB
452 return f;
453}
454
455int git_pathspec_match_workdir(
456 git_pathspec_match_list **out,
457 git_repository *repo,
458 uint32_t flags,
459 git_pathspec *ps)
e91f9a8f
RB
460{
461 int error = 0;
d2ce27dd
RB
462 git_iterator *iter;
463
a8b5f116 464 assert(repo);
e91f9a8f 465
d2ce27dd
RB
466 if (!(error = git_iterator_for_workdir(
467 &iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) {
e91f9a8f 468
d2ce27dd 469 error = pathspec_match_from_iterator(out, iter, flags, ps);
e91f9a8f 470
d2ce27dd
RB
471 git_iterator_free(iter);
472 }
e91f9a8f
RB
473
474 return error;
475}
476
d2ce27dd
RB
477int git_pathspec_match_index(
478 git_pathspec_match_list **out,
479 git_index *index,
480 uint32_t flags,
481 git_pathspec *ps)
482{
483 int error = 0;
484 git_iterator *iter;
485
a8b5f116 486 assert(index);
d2ce27dd
RB
487
488 if (!(error = git_iterator_for_index(
489 &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) {
490
491 error = pathspec_match_from_iterator(out, iter, flags, ps);
492
493 git_iterator_free(iter);
494 }
495
496 return error;
497}
498
499int git_pathspec_match_tree(
500 git_pathspec_match_list **out,
501 git_tree *tree,
502 uint32_t flags,
503 git_pathspec *ps)
504{
505 int error = 0;
506 git_iterator *iter;
507
a8b5f116 508 assert(tree);
d2ce27dd
RB
509
510 if (!(error = git_iterator_for_tree(
511 &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) {
512
513 error = pathspec_match_from_iterator(out, iter, flags, ps);
514
515 git_iterator_free(iter);
516 }
517
518 return error;
519}
520
521void git_pathspec_match_list_free(git_pathspec_match_list *m)
522{
523 pathspec_match_free(m);
524}
525
526size_t git_pathspec_match_list_entrycount(
527 const git_pathspec_match_list *m)
528{
529 return git_array_size(m->matches);
530}
531
532const char *git_pathspec_match_list_entry(
533 const git_pathspec_match_list *m, size_t pos)
534{
535 char **entry = git_array_get(m->matches, pos);
536 return entry ? *entry : NULL;
537}
538
539size_t git_pathspec_match_list_failed_entrycount(
540 const git_pathspec_match_list *m)
541{
542 return git_array_size(m->failures);
543}
544
545const char * git_pathspec_match_list_failed_entry(
546 const git_pathspec_match_list *m, size_t pos)
e91f9a8f 547{
d2ce27dd
RB
548 char **entry = git_array_get(m->failures, pos);
549 return entry ? *entry : NULL;
e91f9a8f 550}