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