]> git.proxmox.com Git - libgit2.git/blob - src/iterator.c
Merge pull request #1239 from ethomson/index_remove
[libgit2.git] / src / iterator.c
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 "iterator.h"
9 #include "tree.h"
10 #include "ignore.h"
11 #include "buffer.h"
12 #include "git2/submodule.h"
13 #include <ctype.h>
14
15 #define ITERATOR_SET_CB(P,NAME_LC) do { \
16 (P)->cb.current = NAME_LC ## _iterator__current; \
17 (P)->cb.at_end = NAME_LC ## _iterator__at_end; \
18 (P)->cb.advance = NAME_LC ## _iterator__advance; \
19 (P)->cb.seek = NAME_LC ## _iterator__seek; \
20 (P)->cb.reset = NAME_LC ## _iterator__reset; \
21 (P)->cb.free = NAME_LC ## _iterator__free; \
22 } while (0)
23
24 #define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \
25 (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \
26 GITERR_CHECK_ALLOC(P); \
27 (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \
28 (P)->base.cb = &(P)->cb; \
29 ITERATOR_SET_CB(P,NAME_LC); \
30 (P)->base.start = start ? git__strdup(start) : NULL; \
31 (P)->base.end = end ? git__strdup(end) : NULL; \
32 if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \
33 git__free(P); return -1; } \
34 (P)->base.prefixcomp = git__prefixcmp; \
35 } while (0)
36
37 static int iterator__reset_range(
38 git_iterator *iter, const char *start, const char *end)
39 {
40 if (start) {
41 if (iter->start)
42 git__free(iter->start);
43 iter->start = git__strdup(start);
44 GITERR_CHECK_ALLOC(iter->start);
45 }
46
47 if (end) {
48 if (iter->end)
49 git__free(iter->end);
50 iter->end = git__strdup(end);
51 GITERR_CHECK_ALLOC(iter->end);
52 }
53
54 return 0;
55 }
56
57 static int iterator_update_ignore_case(
58 git_iterator *iter,
59 git_iterator_flag_t flags)
60 {
61 int error = 0, ignore_case = -1;
62
63 if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
64 ignore_case = true;
65 else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0)
66 ignore_case = false;
67 else {
68 git_index *index;
69
70 if (!(error = git_repository_index__weakptr(&index, iter->repo)))
71 ignore_case = (index->ignore_case != false);
72 }
73
74 if (ignore_case > 0)
75 iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE);
76 else if (ignore_case == 0)
77 iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);
78
79 iter->prefixcomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ?
80 git__prefixcmp_icase : git__prefixcmp;
81
82 return error;
83 }
84
85 static int empty_iterator__no_item(
86 git_iterator *iter, const git_index_entry **entry)
87 {
88 GIT_UNUSED(iter);
89 *entry = NULL;
90 return 0;
91 }
92
93 static int empty_iterator__at_end(git_iterator *iter)
94 {
95 GIT_UNUSED(iter);
96 return 1;
97 }
98
99 static int empty_iterator__reset(
100 git_iterator *iter, const char *start, const char *end)
101 {
102 GIT_UNUSED(iter); GIT_UNUSED(start); GIT_UNUSED(end);
103 return 0;
104 }
105
106 static int empty_iterator__seek(git_iterator *iter, const char *prefix)
107 {
108 GIT_UNUSED(iter); GIT_UNUSED(prefix);
109 return -1;
110 }
111
112 static void empty_iterator__free(git_iterator *iter)
113 {
114 GIT_UNUSED(iter);
115 }
116
117 typedef struct {
118 git_iterator base;
119 git_iterator_callbacks cb;
120 } empty_iterator;
121
122 int git_iterator_for_nothing(git_iterator **iter, git_iterator_flag_t flags)
123 {
124 empty_iterator *i = git__calloc(1, sizeof(empty_iterator));
125 GITERR_CHECK_ALLOC(i);
126
127 i->base.type = GIT_ITERATOR_TYPE_EMPTY;
128 i->base.cb = &i->cb;
129 i->base.flags = flags;
130 i->cb.current = empty_iterator__no_item;
131 i->cb.at_end = empty_iterator__at_end;
132 i->cb.advance = empty_iterator__no_item;
133 i->cb.seek = empty_iterator__seek;
134 i->cb.reset = empty_iterator__reset;
135 i->cb.free = empty_iterator__free;
136
137 *iter = (git_iterator *)i;
138
139 return 0;
140 }
141
142
143 typedef struct tree_iterator_frame tree_iterator_frame;
144 struct tree_iterator_frame {
145 tree_iterator_frame *next, *prev;
146 git_tree *tree;
147 char *start;
148 size_t startlen;
149 size_t index;
150 void **icase_map;
151 void *icase_data[GIT_FLEX_ARRAY];
152 };
153
154 typedef struct {
155 git_iterator base;
156 git_iterator_callbacks cb;
157 tree_iterator_frame *stack, *tail;
158 git_index_entry entry;
159 git_buf path;
160 bool path_has_filename;
161 } tree_iterator;
162
163 GIT_INLINE(const git_tree_entry *)tree_iterator__tree_entry(tree_iterator *ti)
164 {
165 tree_iterator_frame *tf = ti->stack;
166
167 if (tf->index >= git_tree_entrycount(tf->tree))
168 return NULL;
169
170 return git_tree_entry_byindex(
171 tf->tree, tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->index);
172 }
173
174 static char *tree_iterator__current_filename(
175 tree_iterator *ti, const git_tree_entry *te)
176 {
177 if (!ti->path_has_filename) {
178 if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
179 return NULL;
180 ti->path_has_filename = true;
181 }
182
183 return ti->path.ptr;
184 }
185
186 static void tree_iterator__free_frame(tree_iterator_frame *tf)
187 {
188 if (!tf)
189 return;
190
191 git_tree_free(tf->tree);
192 tf->tree = NULL;
193
194 git__free(tf);
195 }
196
197 static bool tree_iterator__pop_frame(tree_iterator *ti)
198 {
199 tree_iterator_frame *tf = ti->stack;
200
201 /* don't free the initial tree/frame */
202 if (!tf->next)
203 return false;
204
205 ti->stack = tf->next;
206 ti->stack->prev = NULL;
207
208 tree_iterator__free_frame(tf);
209
210 return true;
211 }
212
213 static int tree_iterator__to_end(tree_iterator *ti)
214 {
215 while (tree_iterator__pop_frame(ti)) /* pop all */;
216 ti->stack->index = git_tree_entrycount(ti->stack->tree);
217 return 0;
218 }
219
220 static int tree_iterator__current(
221 git_iterator *self, const git_index_entry **entry)
222 {
223 tree_iterator *ti = (tree_iterator *)self;
224 const git_tree_entry *te = tree_iterator__tree_entry(ti);
225
226 if (entry)
227 *entry = NULL;
228
229 if (te == NULL)
230 return 0;
231
232 ti->entry.mode = te->attr;
233 git_oid_cpy(&ti->entry.oid, &te->oid);
234
235 ti->entry.path = tree_iterator__current_filename(ti, te);
236 if (ti->entry.path == NULL)
237 return -1;
238
239 if (ti->base.end && ti->base.prefixcomp(ti->entry.path, ti->base.end) > 0)
240 return tree_iterator__to_end(ti);
241
242 if (entry)
243 *entry = &ti->entry;
244
245 return 0;
246 }
247
248 static int tree_iterator__at_end(git_iterator *self)
249 {
250 return (tree_iterator__tree_entry((tree_iterator *)self) == NULL);
251 }
252
253 static int tree_iterator__icase_map_cmp(const void *a, const void *b, void *data)
254 {
255 git_tree *tree = data;
256 const git_tree_entry *te1 = git_tree_entry_byindex(tree, (size_t)a);
257 const git_tree_entry *te2 = git_tree_entry_byindex(tree, (size_t)b);
258 return te1 ? (te2 ? git_tree_entry_icmp(te1, te2) : 1) : -1;
259 }
260
261 static int tree_iterator__frame_start_icmp(const void *key, const void *element)
262 {
263 const tree_iterator_frame *tf = (const tree_iterator_frame *)key;
264 const git_tree_entry *te = git_tree_entry_byindex(tf->tree, (size_t)element);
265
266 return memcmp(tf->start, te->filename, min(tf->startlen, te->filename_len));
267 }
268
269 static void tree_iterator__frame_seek_start(tree_iterator_frame *tf)
270 {
271 if (!tf->start)
272 tf->index = 0;
273 else if (!tf->icase_map)
274 tf->index = git_tree__prefix_position(tf->tree, tf->start);
275 else {
276 if (!git__bsearch(
277 tf->icase_map, git_tree_entrycount(tf->tree),
278 tf, tree_iterator__frame_start_icmp, &tf->index))
279 {
280 while (tf->index > 0) {
281 /* move back while previous entry is still prefixed */
282 if (tree_iterator__frame_start_icmp(
283 tf, (const void *)(tf->index - 1)))
284 break;
285 tf->index--;
286 }
287 }
288 }
289 }
290
291 static tree_iterator_frame *tree_iterator__alloc_frame(
292 tree_iterator *ti, git_tree *tree, char *start)
293 {
294 size_t i, max_i = git_tree_entrycount(tree);
295 tree_iterator_frame *tf =
296 git__calloc(1, sizeof(tree_iterator_frame) + max_i * sizeof(void *));
297 if (!tf)
298 return NULL;
299
300 tf->tree = tree;
301
302 if (start && *start) {
303 tf->start = start;
304 tf->startlen = strlen(start);
305 }
306
307 if (!max_i)
308 return tf;
309
310 if ((ti->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0) {
311 tf->icase_map = tf->icase_data;
312
313 for (i = 0; i < max_i; ++i)
314 tf->icase_map[i] = (void *)i;
315
316 git__tsort_r(
317 tf->icase_map, max_i, tree_iterator__icase_map_cmp, tf->tree);
318 }
319
320 tree_iterator__frame_seek_start(tf);
321
322 return tf;
323 }
324
325 static int tree_iterator__expand_tree(tree_iterator *ti)
326 {
327 int error;
328 git_tree *subtree;
329 const git_tree_entry *te = tree_iterator__tree_entry(ti);
330 tree_iterator_frame *tf;
331 char *relpath;
332
333 while (te != NULL && git_tree_entry__is_tree(te)) {
334 if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
335 return -1;
336
337 /* check that we have not passed the range end */
338 if (ti->base.end != NULL &&
339 ti->base.prefixcomp(ti->path.ptr, ti->base.end) > 0)
340 return tree_iterator__to_end(ti);
341
342 if ((error = git_tree_lookup(&subtree, ti->base.repo, &te->oid)) < 0)
343 return error;
344
345 relpath = NULL;
346
347 /* apply range start to new frame if relevant */
348 if (ti->stack->start &&
349 ti->base.prefixcomp(ti->stack->start, te->filename) == 0)
350 {
351 if (ti->stack->start[te->filename_len] == '/')
352 relpath = ti->stack->start + te->filename_len + 1;
353 }
354
355 if ((tf = tree_iterator__alloc_frame(ti, subtree, relpath)) == NULL)
356 return -1;
357
358 tf->next = ti->stack;
359 ti->stack = tf;
360 tf->next->prev = tf;
361
362 te = tree_iterator__tree_entry(ti);
363 }
364
365 return 0;
366 }
367
368 static int tree_iterator__advance(
369 git_iterator *self, const git_index_entry **entry)
370 {
371 int error = 0;
372 tree_iterator *ti = (tree_iterator *)self;
373 const git_tree_entry *te = NULL;
374
375 if (entry != NULL)
376 *entry = NULL;
377
378 if (ti->path_has_filename) {
379 git_buf_rtruncate_at_char(&ti->path, '/');
380 ti->path_has_filename = false;
381 }
382
383 while (1) {
384 ++ti->stack->index;
385
386 if ((te = tree_iterator__tree_entry(ti)) != NULL)
387 break;
388
389 if (!tree_iterator__pop_frame(ti))
390 break; /* no frames left to pop */
391
392 git_buf_rtruncate_at_char(&ti->path, '/');
393 }
394
395 if (te && git_tree_entry__is_tree(te))
396 error = tree_iterator__expand_tree(ti);
397
398 if (!error)
399 error = tree_iterator__current(self, entry);
400
401 return error;
402 }
403
404 static int tree_iterator__seek(git_iterator *self, const char *prefix)
405 {
406 GIT_UNUSED(self);
407 GIT_UNUSED(prefix);
408 /* pop stack until matches prefix */
409 /* seek item in current frame matching prefix */
410 /* push stack which matches prefix */
411 return -1;
412 }
413
414 static void tree_iterator__free(git_iterator *self)
415 {
416 tree_iterator *ti = (tree_iterator *)self;
417
418 while (tree_iterator__pop_frame(ti)) /* pop all */;
419
420 tree_iterator__free_frame(ti->stack);
421 ti->stack = ti->tail = NULL;
422
423 git_buf_free(&ti->path);
424 }
425
426 static int tree_iterator__reset(
427 git_iterator *self, const char *start, const char *end)
428 {
429 tree_iterator *ti = (tree_iterator *)self;
430
431 while (tree_iterator__pop_frame(ti)) /* pop all */;
432
433 if (iterator__reset_range(self, start, end) < 0)
434 return -1;
435
436 /* reset start position */
437 tree_iterator__frame_seek_start(ti->stack);
438
439 git_buf_clear(&ti->path);
440 ti->path_has_filename = false;
441
442 return tree_iterator__expand_tree(ti);
443 }
444
445 int git_iterator_for_tree_range(
446 git_iterator **iter,
447 git_tree *tree,
448 git_iterator_flag_t flags,
449 const char *start,
450 const char *end)
451 {
452 int error;
453 tree_iterator *ti;
454
455 if (tree == NULL)
456 return git_iterator_for_nothing(iter, flags);
457
458 if ((error = git_tree__dup(&tree, tree)) < 0)
459 return error;
460
461 ITERATOR_BASE_INIT(ti, tree, TREE);
462
463 ti->base.repo = git_tree_owner(tree);
464
465 if ((error = iterator_update_ignore_case((git_iterator *)ti, flags)) < 0)
466 goto fail;
467
468 ti->stack = ti->tail = tree_iterator__alloc_frame(ti, tree, ti->base.start);
469
470 if ((error = tree_iterator__expand_tree(ti)) < 0)
471 goto fail;
472
473 *iter = (git_iterator *)ti;
474 return 0;
475
476 fail:
477 git_iterator_free((git_iterator *)ti);
478 return error;
479 }
480
481
482 typedef struct {
483 git_iterator base;
484 git_iterator_callbacks cb;
485 git_index *index;
486 size_t current;
487 } index_iterator;
488
489 static int index_iterator__current(
490 git_iterator *self, const git_index_entry **entry)
491 {
492 index_iterator *ii = (index_iterator *)self;
493 const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
494
495 if (entry)
496 *entry = ie;
497
498 return 0;
499 }
500
501 static int index_iterator__at_end(git_iterator *self)
502 {
503 index_iterator *ii = (index_iterator *)self;
504 return (ii->current >= git_index_entrycount(ii->index));
505 }
506
507 static void index_iterator__skip_conflicts(
508 index_iterator *ii)
509 {
510 size_t entrycount = git_index_entrycount(ii->index);
511 const git_index_entry *ie;
512
513 while (ii->current < entrycount) {
514 ie = git_index_get_byindex(ii->index, ii->current);
515
516 if (ie == NULL ||
517 (ii->base.end != NULL &&
518 ii->base.prefixcomp(ie->path, ii->base.end) > 0)) {
519 ii->current = entrycount;
520 break;
521 }
522
523 if (git_index_entry_stage(ie) == 0)
524 break;
525
526 ii->current++;
527 }
528 }
529
530 static int index_iterator__advance(
531 git_iterator *self, const git_index_entry **entry)
532 {
533 index_iterator *ii = (index_iterator *)self;
534
535 if (ii->current < git_index_entrycount(ii->index))
536 ii->current++;
537
538 index_iterator__skip_conflicts(ii);
539
540 return index_iterator__current(self, entry);
541 }
542
543 static int index_iterator__seek(git_iterator *self, const char *prefix)
544 {
545 GIT_UNUSED(self);
546 GIT_UNUSED(prefix);
547 /* find last item before prefix */
548 return -1;
549 }
550
551 static int index_iterator__reset(
552 git_iterator *self, const char *start, const char *end)
553 {
554 index_iterator *ii = (index_iterator *)self;
555 if (iterator__reset_range(self, start, end) < 0)
556 return -1;
557 ii->current = ii->base.start ?
558 git_index__prefix_position(ii->index, ii->base.start) : 0;
559 index_iterator__skip_conflicts(ii);
560 return 0;
561 }
562
563 static void index_iterator__free(git_iterator *self)
564 {
565 index_iterator *ii = (index_iterator *)self;
566 git_index_free(ii->index);
567 ii->index = NULL;
568 }
569
570 int git_iterator_for_index_range(
571 git_iterator **iter,
572 git_index *index,
573 git_iterator_flag_t flags,
574 const char *start,
575 const char *end)
576 {
577 index_iterator *ii;
578
579 GIT_UNUSED(flags);
580
581 ITERATOR_BASE_INIT(ii, index, INDEX);
582
583 ii->base.repo = git_index_owner(index);
584 if (index->ignore_case) {
585 ii->base.flags |= GIT_ITERATOR_IGNORE_CASE;
586 ii->base.prefixcomp = git__prefixcmp_icase;
587 }
588 ii->index = index;
589 GIT_REFCOUNT_INC(index);
590
591 index_iterator__reset((git_iterator *)ii, NULL, NULL);
592
593 *iter = (git_iterator *)ii;
594
595 return 0;
596 }
597
598
599 typedef struct workdir_iterator_frame workdir_iterator_frame;
600 struct workdir_iterator_frame {
601 workdir_iterator_frame *next;
602 git_vector entries;
603 size_t index;
604 };
605
606 typedef struct {
607 git_iterator base;
608 git_iterator_callbacks cb;
609 workdir_iterator_frame *stack;
610 int (*entrycmp)(const void *pfx, const void *item);
611 git_ignores ignores;
612 git_index_entry entry;
613 git_buf path;
614 size_t root_len;
615 int is_ignored;
616 } workdir_iterator;
617
618 GIT_INLINE(bool) path_is_dotgit(const git_path_with_stat *ps)
619 {
620 if (!ps)
621 return false;
622 else {
623 const char *path = ps->path;
624 size_t len = ps->path_len;
625
626 if (len < 4)
627 return false;
628 if (path[len - 1] == '/')
629 len--;
630 if (tolower(path[len - 1]) != 't' ||
631 tolower(path[len - 2]) != 'i' ||
632 tolower(path[len - 3]) != 'g' ||
633 tolower(path[len - 4]) != '.')
634 return false;
635 return (len == 4 || path[len - 5] == '/');
636 }
637 }
638
639 static workdir_iterator_frame *workdir_iterator__alloc_frame(
640 workdir_iterator *wi)
641 {
642 workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
643 git_vector_cmp entry_compare = CASESELECT(
644 (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0,
645 git_path_with_stat_cmp_icase, git_path_with_stat_cmp);
646
647 if (wf == NULL)
648 return NULL;
649
650 if (git_vector_init(&wf->entries, 0, entry_compare) != 0) {
651 git__free(wf);
652 return NULL;
653 }
654
655 return wf;
656 }
657
658 static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
659 {
660 unsigned int i;
661 git_path_with_stat *path;
662
663 git_vector_foreach(&wf->entries, i, path)
664 git__free(path);
665 git_vector_free(&wf->entries);
666 git__free(wf);
667 }
668
669 static int workdir_iterator__update_entry(workdir_iterator *wi);
670
671 static int workdir_iterator__entry_cmp_case(const void *pfx, const void *item)
672 {
673 const git_path_with_stat *ps = item;
674 return git__prefixcmp((const char *)pfx, ps->path);
675 }
676
677 static int workdir_iterator__entry_cmp_icase(const void *pfx, const void *item)
678 {
679 const git_path_with_stat *ps = item;
680 return git__prefixcmp_icase((const char *)pfx, ps->path);
681 }
682
683 static void workdir_iterator__seek_frame_start(
684 workdir_iterator *wi, workdir_iterator_frame *wf)
685 {
686 if (!wf)
687 return;
688
689 if (wi->base.start)
690 git_vector_bsearch3(
691 &wf->index, &wf->entries, wi->entrycmp, wi->base.start);
692 else
693 wf->index = 0;
694
695 if (path_is_dotgit(git_vector_get(&wf->entries, wf->index)))
696 wf->index++;
697 }
698
699 static int workdir_iterator__expand_dir(workdir_iterator *wi)
700 {
701 int error;
702 workdir_iterator_frame *wf = workdir_iterator__alloc_frame(wi);
703 GITERR_CHECK_ALLOC(wf);
704
705 error = git_path_dirload_with_stat(
706 wi->path.ptr, wi->root_len,
707 (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0,
708 wi->base.start, wi->base.end, &wf->entries);
709
710 if (error < 0 || wf->entries.length == 0) {
711 workdir_iterator__free_frame(wf);
712 return GIT_ENOTFOUND;
713 }
714
715 workdir_iterator__seek_frame_start(wi, wf);
716
717 /* only push new ignores if this is not top level directory */
718 if (wi->stack != NULL) {
719 ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/');
720 (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]);
721 }
722
723 wf->next = wi->stack;
724 wi->stack = wf;
725
726 return workdir_iterator__update_entry(wi);
727 }
728
729 static int workdir_iterator__current(
730 git_iterator *self, const git_index_entry **entry)
731 {
732 workdir_iterator *wi = (workdir_iterator *)self;
733 *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
734 return 0;
735 }
736
737 static int workdir_iterator__at_end(git_iterator *self)
738 {
739 return (((workdir_iterator *)self)->entry.path == NULL);
740 }
741
742 static int workdir_iterator__advance(
743 git_iterator *self, const git_index_entry **entry)
744 {
745 int error;
746 workdir_iterator *wi = (workdir_iterator *)self;
747 workdir_iterator_frame *wf;
748 git_path_with_stat *next;
749
750 if (entry != NULL)
751 *entry = NULL;
752
753 if (wi->entry.path == NULL)
754 return 0;
755
756 while (1) {
757 wf = wi->stack;
758 next = git_vector_get(&wf->entries, ++wf->index);
759
760 if (next != NULL) {
761 /* match git's behavior of ignoring anything named ".git" */
762 if (path_is_dotgit(next))
763 continue;
764 /* else found a good entry */
765 break;
766 }
767
768 /* pop stack if anything is left to pop */
769 if (!wf->next) {
770 memset(&wi->entry, 0, sizeof(wi->entry));
771 return 0;
772 }
773
774 wi->stack = wf->next;
775 workdir_iterator__free_frame(wf);
776 git_ignore__pop_dir(&wi->ignores);
777 }
778
779 error = workdir_iterator__update_entry(wi);
780
781 if (!error && entry != NULL)
782 error = workdir_iterator__current(self, entry);
783
784 return error;
785 }
786
787 static int workdir_iterator__seek(git_iterator *self, const char *prefix)
788 {
789 GIT_UNUSED(self);
790 GIT_UNUSED(prefix);
791 /* pop stack until matching prefix */
792 /* find prefix item in current frame */
793 /* push subdirectories as deep as possible while matching */
794 return 0;
795 }
796
797 static int workdir_iterator__reset(
798 git_iterator *self, const char *start, const char *end)
799 {
800 workdir_iterator *wi = (workdir_iterator *)self;
801
802 while (wi->stack != NULL && wi->stack->next != NULL) {
803 workdir_iterator_frame *wf = wi->stack;
804 wi->stack = wf->next;
805 workdir_iterator__free_frame(wf);
806 git_ignore__pop_dir(&wi->ignores);
807 }
808
809 if (iterator__reset_range(self, start, end) < 0)
810 return -1;
811
812 workdir_iterator__seek_frame_start(wi, wi->stack);
813
814 return workdir_iterator__update_entry(wi);
815 }
816
817 static void workdir_iterator__free(git_iterator *self)
818 {
819 workdir_iterator *wi = (workdir_iterator *)self;
820
821 while (wi->stack != NULL) {
822 workdir_iterator_frame *wf = wi->stack;
823 wi->stack = wf->next;
824 workdir_iterator__free_frame(wf);
825 }
826
827 git_ignore__free(&wi->ignores);
828 git_buf_free(&wi->path);
829 }
830
831 static int workdir_iterator__update_entry(workdir_iterator *wi)
832 {
833 git_path_with_stat *ps =
834 git_vector_get(&wi->stack->entries, wi->stack->index);
835
836 git_buf_truncate(&wi->path, wi->root_len);
837 memset(&wi->entry, 0, sizeof(wi->entry));
838
839 if (!ps)
840 return 0;
841
842 if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0)
843 return -1;
844
845 if (wi->base.end &&
846 wi->base.prefixcomp(wi->path.ptr + wi->root_len, wi->base.end) > 0)
847 return 0;
848
849 wi->entry.path = ps->path;
850
851 /* skip over .git entries */
852 if (path_is_dotgit(ps))
853 return workdir_iterator__advance((git_iterator *)wi, NULL);
854
855 wi->is_ignored = -1;
856
857 git_index_entry__init_from_stat(&wi->entry, &ps->st);
858
859 /* need different mode here to keep directories during iteration */
860 wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
861
862 /* if this is a file type we don't handle, treat as ignored */
863 if (wi->entry.mode == 0) {
864 wi->is_ignored = 1;
865 return 0;
866 }
867
868 /* detect submodules */
869 if (S_ISDIR(wi->entry.mode)) {
870 int res = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path);
871 bool is_submodule = (res == 0);
872 if (res == GIT_ENOTFOUND)
873 giterr_clear();
874
875 /* if submodule, mark as GITLINK and remove trailing slash */
876 if (is_submodule) {
877 size_t len = strlen(wi->entry.path);
878 assert(wi->entry.path[len - 1] == '/');
879 wi->entry.path[len - 1] = '\0';
880 wi->entry.mode = S_IFGITLINK;
881 }
882 }
883
884 return 0;
885 }
886
887 int git_iterator_for_workdir_range(
888 git_iterator **iter,
889 git_repository *repo,
890 git_iterator_flag_t flags,
891 const char *start,
892 const char *end)
893 {
894 int error;
895 workdir_iterator *wi;
896
897 assert(iter && repo);
898
899 if ((error = git_repository__ensure_not_bare(
900 repo, "scan working directory")) < 0)
901 return error;
902
903 ITERATOR_BASE_INIT(wi, workdir, WORKDIR);
904 wi->base.repo = repo;
905
906 if ((error = iterator_update_ignore_case((git_iterator *)wi, flags)) < 0)
907 goto fail;
908
909 if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||
910 git_path_to_dir(&wi->path) < 0 ||
911 git_ignore__for_path(repo, "", &wi->ignores) < 0)
912 {
913 git__free(wi);
914 return -1;
915 }
916
917 wi->root_len = wi->path.size;
918 wi->entrycmp = (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0 ?
919 workdir_iterator__entry_cmp_icase : workdir_iterator__entry_cmp_case;
920
921 if ((error = workdir_iterator__expand_dir(wi)) < 0) {
922 if (error != GIT_ENOTFOUND)
923 goto fail;
924 giterr_clear();
925 }
926
927 *iter = (git_iterator *)wi;
928 return 0;
929
930 fail:
931 git_iterator_free((git_iterator *)wi);
932 return error;
933 }
934
935
936 typedef struct {
937 /* replacement callbacks */
938 git_iterator_callbacks cb;
939 /* original iterator values */
940 git_iterator_callbacks *orig;
941 git_iterator_type_t orig_type;
942 /* spoolandsort data */
943 git_vector entries;
944 git_pool entry_pool;
945 git_pool string_pool;
946 size_t position;
947 } spoolandsort_callbacks;
948
949 static int spoolandsort_iterator__current(
950 git_iterator *self, const git_index_entry **entry)
951 {
952 spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
953
954 *entry = (const git_index_entry *)
955 git_vector_get(&scb->entries, scb->position);
956
957 return 0;
958 }
959
960 static int spoolandsort_iterator__at_end(git_iterator *self)
961 {
962 spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
963
964 return 0 == scb->entries.length || scb->entries.length - 1 <= scb->position;
965 }
966
967 static int spoolandsort_iterator__advance(
968 git_iterator *self, const git_index_entry **entry)
969 {
970 spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
971
972 *entry = (const git_index_entry *)
973 git_vector_get(&scb->entries, ++scb->position);
974
975 return 0;
976 }
977
978 static int spoolandsort_iterator__seek(git_iterator *self, const char *prefix)
979 {
980 GIT_UNUSED(self);
981 GIT_UNUSED(prefix);
982
983 return -1;
984 }
985
986 static int spoolandsort_iterator__reset(
987 git_iterator *self, const char *start, const char *end)
988 {
989 spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
990
991 GIT_UNUSED(start); GIT_UNUSED(end);
992
993 scb->position = 0;
994
995 return 0;
996 }
997
998 static void spoolandsort_iterator__free_callbacks(spoolandsort_callbacks *scb)
999 {
1000 git_pool_clear(&scb->string_pool);
1001 git_pool_clear(&scb->entry_pool);
1002 git_vector_free(&scb->entries);
1003 git__free(scb);
1004 }
1005
1006 void git_iterator_spoolandsort_pop(git_iterator *self)
1007 {
1008 spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb;
1009
1010 if (self->type != GIT_ITERATOR_TYPE_SPOOLANDSORT)
1011 return;
1012
1013 self->cb = scb->orig;
1014 self->type = scb->orig_type;
1015 self->flags ^= GIT_ITERATOR_IGNORE_CASE;
1016
1017 spoolandsort_iterator__free_callbacks(scb);
1018 }
1019
1020 static void spoolandsort_iterator__free(git_iterator *self)
1021 {
1022 git_iterator_spoolandsort_pop(self);
1023 self->cb->free(self);
1024 }
1025
1026 int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case)
1027 {
1028 const git_index_entry *item;
1029 spoolandsort_callbacks *scb;
1030 int (*entrycomp)(const void *a, const void *b);
1031
1032 if (((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) == (ignore_case != 0))
1033 return 0;
1034
1035 if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {
1036 iter->flags = (iter->flags ^ GIT_ITERATOR_IGNORE_CASE);
1037 return 0;
1038 }
1039
1040 scb = git__calloc(1, sizeof(spoolandsort_callbacks));
1041 GITERR_CHECK_ALLOC(scb);
1042
1043 ITERATOR_SET_CB(scb,spoolandsort);
1044
1045 scb->orig = iter->cb;
1046 scb->orig_type = iter->type;
1047 scb->position = 0;
1048
1049 entrycomp = ignore_case ? git_index_entry__cmp_icase : git_index_entry__cmp;
1050
1051 if (git_vector_init(&scb->entries, 16, entrycomp) < 0 ||
1052 git_pool_init(&scb->entry_pool, sizeof(git_index_entry), 0) < 0 ||
1053 git_pool_init(&scb->string_pool, 1, 0) < 0 ||
1054 git_iterator_current(iter, &item) < 0)
1055 goto fail;
1056
1057 while (item) {
1058 git_index_entry *clone = git_pool_malloc(&scb->entry_pool, 1);
1059 if (!clone)
1060 goto fail;
1061
1062 memcpy(clone, item, sizeof(git_index_entry));
1063
1064 if (item->path) {
1065 clone->path = git_pool_strdup(&scb->string_pool, item->path);
1066 if (!clone->path)
1067 goto fail;
1068 }
1069
1070 if (git_vector_insert(&scb->entries, clone) < 0)
1071 goto fail;
1072
1073 if (git_iterator_advance(iter, &item) < 0)
1074 goto fail;
1075 }
1076
1077 git_vector_sort(&scb->entries);
1078
1079 iter->cb = (git_iterator_callbacks *)scb;
1080 iter->type = GIT_ITERATOR_TYPE_SPOOLANDSORT;
1081 iter->flags ^= GIT_ITERATOR_IGNORE_CASE;
1082
1083 return 0;
1084
1085 fail:
1086 spoolandsort_iterator__free_callbacks(scb);
1087 return -1;
1088 }
1089
1090
1091 void git_iterator_free(git_iterator *iter)
1092 {
1093 if (iter == NULL)
1094 return;
1095
1096 iter->cb->free(iter);
1097
1098 git__free(iter->start);
1099 git__free(iter->end);
1100
1101 memset(iter, 0, sizeof(*iter));
1102
1103 git__free(iter);
1104 }
1105
1106 git_index *git_iterator_index_get_index(git_iterator *iter)
1107 {
1108 if (iter->type == GIT_ITERATOR_TYPE_INDEX)
1109 return ((index_iterator *)iter)->index;
1110
1111 if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT &&
1112 ((spoolandsort_callbacks *)iter->cb)->orig_type == GIT_ITERATOR_TYPE_INDEX)
1113 return ((index_iterator *)iter)->index;
1114
1115 return NULL;
1116 }
1117
1118 git_iterator_type_t git_iterator_inner_type(git_iterator *iter)
1119 {
1120 if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT)
1121 return ((spoolandsort_callbacks *)iter->cb)->orig_type;
1122
1123 return iter->type;
1124 }
1125
1126 int git_iterator_current_tree_entry(
1127 git_iterator *iter, const git_tree_entry **tree_entry)
1128 {
1129 *tree_entry = (iter->type != GIT_ITERATOR_TYPE_TREE) ? NULL :
1130 tree_iterator__tree_entry((tree_iterator *)iter);
1131 return 0;
1132 }
1133
1134 int git_iterator_current_parent_tree(
1135 git_iterator *iter,
1136 const char *parent_path,
1137 const git_tree **tree_ptr)
1138 {
1139 tree_iterator *ti = (tree_iterator *)iter;
1140 tree_iterator_frame *tf;
1141 const char *scan = parent_path;
1142 int (*strncomp)(const char *a, const char *b, size_t sz);
1143
1144 if (iter->type != GIT_ITERATOR_TYPE_TREE || ti->stack == NULL)
1145 goto notfound;
1146
1147 strncomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ?
1148 git__strncasecmp : git__strncmp;
1149
1150 for (tf = ti->tail; tf != NULL; tf = tf->prev) {
1151 const git_tree_entry *te;
1152
1153 if (!*scan) {
1154 *tree_ptr = tf->tree;
1155 return 0;
1156 }
1157
1158 te = git_tree_entry_byindex(tf->tree,
1159 tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->index);
1160
1161 if (strncomp(scan, te->filename, te->filename_len) != 0)
1162 goto notfound;
1163
1164 scan += te->filename_len;
1165
1166 if (*scan) {
1167 if (*scan != '/')
1168 goto notfound;
1169 scan++;
1170 }
1171 }
1172
1173 notfound:
1174 *tree_ptr = NULL;
1175 return 0;
1176 }
1177
1178 int git_iterator_current_is_ignored(git_iterator *iter)
1179 {
1180 workdir_iterator *wi = (workdir_iterator *)iter;
1181
1182 if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
1183 return 0;
1184
1185 if (wi->is_ignored != -1)
1186 return wi->is_ignored;
1187
1188 if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
1189 wi->is_ignored = 1;
1190
1191 return wi->is_ignored;
1192 }
1193
1194 int git_iterator_advance_into_directory(
1195 git_iterator *iter, const git_index_entry **entry)
1196 {
1197 workdir_iterator *wi = (workdir_iterator *)iter;
1198
1199 if (iter->type == GIT_ITERATOR_TYPE_WORKDIR &&
1200 wi->entry.path &&
1201 (wi->entry.mode == GIT_FILEMODE_TREE ||
1202 wi->entry.mode == GIT_FILEMODE_COMMIT))
1203 {
1204 if (workdir_iterator__expand_dir(wi) < 0)
1205 /* if error loading or if empty, skip the directory. */
1206 return workdir_iterator__advance(iter, entry);
1207 }
1208
1209 return entry ? git_iterator_current(iter, entry) : 0;
1210 }
1211
1212 int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
1213 {
1214 const git_index_entry *entry;
1215
1216 /* a "done" iterator is after every prefix */
1217 if (git_iterator_current(iter, &entry) < 0 ||
1218 entry == NULL)
1219 return 1;
1220
1221 /* a NULL prefix is after any valid iterator */
1222 if (!path_prefix)
1223 return -1;
1224
1225 return iter->prefixcomp(entry->path, path_prefix);
1226 }
1227
1228 int git_iterator_current_workdir_path(git_iterator *iter, git_buf **path)
1229 {
1230 workdir_iterator *wi = (workdir_iterator *)iter;
1231
1232 if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->entry.path)
1233 *path = NULL;
1234 else
1235 *path = &wi->path;
1236
1237 return 0;
1238 }
1239