rugged 0.24.6.1 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (213) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/ext/rugged/extconf.rb +9 -2
  4. data/ext/rugged/rugged.c +85 -21
  5. data/ext/rugged/rugged.h +7 -21
  6. data/ext/rugged/rugged_backend.c +3 -20
  7. data/ext/rugged/rugged_blame.c +7 -24
  8. data/ext/rugged/rugged_blob.c +136 -59
  9. data/ext/rugged/rugged_branch.c +3 -20
  10. data/ext/rugged/rugged_branch_collection.c +3 -20
  11. data/ext/rugged/rugged_commit.c +251 -101
  12. data/ext/rugged/rugged_config.c +3 -20
  13. data/ext/rugged/rugged_cred.c +3 -20
  14. data/ext/rugged/rugged_diff.c +3 -20
  15. data/ext/rugged/rugged_diff_delta.c +3 -20
  16. data/ext/rugged/rugged_diff_hunk.c +3 -20
  17. data/ext/rugged/rugged_diff_line.c +3 -20
  18. data/ext/rugged/rugged_index.c +46 -229
  19. data/ext/rugged/rugged_note.c +3 -20
  20. data/ext/rugged/rugged_object.c +3 -20
  21. data/ext/rugged/rugged_patch.c +192 -34
  22. data/ext/rugged/rugged_rebase.c +90 -48
  23. data/ext/rugged/rugged_reference.c +4 -21
  24. data/ext/rugged/rugged_reference_collection.c +3 -20
  25. data/ext/rugged/rugged_remote.c +70 -42
  26. data/ext/rugged/rugged_remote_collection.c +3 -20
  27. data/ext/rugged/rugged_repo.c +50 -59
  28. data/ext/rugged/rugged_revwalk.c +4 -21
  29. data/ext/rugged/rugged_settings.c +3 -20
  30. data/ext/rugged/rugged_signature.c +3 -20
  31. data/ext/rugged/rugged_submodule.c +4 -21
  32. data/ext/rugged/rugged_submodule_collection.c +3 -20
  33. data/ext/rugged/rugged_tag.c +3 -20
  34. data/ext/rugged/rugged_tag_collection.c +3 -20
  35. data/ext/rugged/rugged_tree.c +189 -184
  36. data/lib/rugged/attributes.rb +5 -0
  37. data/lib/rugged/blob.rb +5 -0
  38. data/lib/rugged/branch.rb +6 -1
  39. data/lib/rugged/commit.rb +5 -0
  40. data/lib/rugged/console.rb +5 -0
  41. data/lib/rugged/credentials.rb +5 -0
  42. data/lib/rugged/diff/delta.rb +5 -0
  43. data/lib/rugged/diff/hunk.rb +5 -0
  44. data/lib/rugged/diff/line.rb +5 -0
  45. data/lib/rugged/diff.rb +5 -0
  46. data/lib/rugged/index.rb +120 -0
  47. data/lib/rugged/object.rb +5 -0
  48. data/lib/rugged/patch.rb +5 -0
  49. data/lib/rugged/reference.rb +5 -0
  50. data/lib/rugged/remote.rb +5 -0
  51. data/lib/rugged/repository.rb +9 -4
  52. data/lib/rugged/submodule_collection.rb +5 -0
  53. data/lib/rugged/tag.rb +5 -0
  54. data/lib/rugged/tree.rb +156 -1
  55. data/lib/rugged/version.rb +6 -1
  56. data/lib/rugged/walker.rb +5 -0
  57. data/lib/rugged.rb +5 -0
  58. data/vendor/libgit2/CMakeLists.txt +12 -2
  59. data/vendor/libgit2/include/git2/blob.h +39 -28
  60. data/vendor/libgit2/include/git2/commit.h +76 -0
  61. data/vendor/libgit2/include/git2/common.h +21 -1
  62. data/vendor/libgit2/include/git2/describe.h +5 -2
  63. data/vendor/libgit2/include/git2/diff.h +62 -7
  64. data/vendor/libgit2/include/git2/errors.h +2 -1
  65. data/vendor/libgit2/include/git2/index.h +25 -0
  66. data/vendor/libgit2/include/git2/merge.h +10 -1
  67. data/vendor/libgit2/include/git2/odb.h +47 -1
  68. data/vendor/libgit2/include/git2/pack.h +4 -4
  69. data/vendor/libgit2/include/git2/patch.h +1 -1
  70. data/vendor/libgit2/include/git2/proxy.h +92 -0
  71. data/vendor/libgit2/include/git2/refs.h +11 -0
  72. data/vendor/libgit2/include/git2/remote.h +21 -8
  73. data/vendor/libgit2/include/git2/repository.h +20 -1
  74. data/vendor/libgit2/include/git2/revwalk.h +4 -6
  75. data/vendor/libgit2/include/git2/signature.h +13 -0
  76. data/vendor/libgit2/include/git2/submodule.h +11 -3
  77. data/vendor/libgit2/include/git2/sys/merge.h +177 -0
  78. data/vendor/libgit2/include/git2/sys/odb_backend.h +11 -0
  79. data/vendor/libgit2/include/git2/sys/remote.h +16 -0
  80. data/vendor/libgit2/include/git2/sys/stream.h +2 -1
  81. data/vendor/libgit2/include/git2/sys/time.h +31 -0
  82. data/vendor/libgit2/include/git2/sys/transport.h +3 -1
  83. data/vendor/libgit2/include/git2/tag.h +9 -0
  84. data/vendor/libgit2/include/git2/transaction.h +9 -0
  85. data/vendor/libgit2/include/git2/tree.h +55 -0
  86. data/vendor/libgit2/include/git2/version.h +4 -4
  87. data/vendor/libgit2/include/git2.h +1 -0
  88. data/vendor/libgit2/src/annotated_commit.c +99 -80
  89. data/vendor/libgit2/src/annotated_commit.h +5 -2
  90. data/vendor/libgit2/src/apply.c +377 -0
  91. data/vendor/libgit2/src/apply.h +21 -0
  92. data/vendor/libgit2/src/array.h +0 -1
  93. data/vendor/libgit2/src/blob.c +71 -39
  94. data/vendor/libgit2/src/branch.c +7 -5
  95. data/vendor/libgit2/src/buffer.c +252 -20
  96. data/vendor/libgit2/src/buffer.h +8 -0
  97. data/vendor/libgit2/src/checkout.c +69 -42
  98. data/vendor/libgit2/src/clone.c +0 -8
  99. data/vendor/libgit2/src/commit.c +193 -49
  100. data/vendor/libgit2/src/commit_list.c +8 -3
  101. data/vendor/libgit2/src/commit_list.h +1 -0
  102. data/vendor/libgit2/src/common.h +2 -1
  103. data/vendor/libgit2/src/config.c +3 -3
  104. data/vendor/libgit2/src/config_file.c +20 -10
  105. data/vendor/libgit2/src/crlf.c +1 -0
  106. data/vendor/libgit2/src/curl_stream.c +106 -6
  107. data/vendor/libgit2/src/delta.c +238 -62
  108. data/vendor/libgit2/src/delta.h +79 -58
  109. data/vendor/libgit2/src/describe.c +1 -1
  110. data/vendor/libgit2/src/diff.c +32 -1554
  111. data/vendor/libgit2/src/diff.h +14 -122
  112. data/vendor/libgit2/src/diff_driver.c +4 -6
  113. data/vendor/libgit2/src/diff_file.c +3 -0
  114. data/vendor/libgit2/src/diff_generate.c +1613 -0
  115. data/vendor/libgit2/src/diff_generate.h +123 -0
  116. data/vendor/libgit2/src/diff_parse.c +101 -0
  117. data/vendor/libgit2/src/diff_parse.h +18 -0
  118. data/vendor/libgit2/src/diff_print.c +263 -144
  119. data/vendor/libgit2/src/diff_stats.c +21 -12
  120. data/vendor/libgit2/src/diff_tform.c +1 -0
  121. data/vendor/libgit2/src/diff_tform.h +22 -0
  122. data/vendor/libgit2/src/diff_xdiff.c +9 -9
  123. data/vendor/libgit2/src/diff_xdiff.h +5 -5
  124. data/vendor/libgit2/src/fetchhead.c +8 -8
  125. data/vendor/libgit2/src/filebuf.c +6 -1
  126. data/vendor/libgit2/src/filebuf.h +1 -0
  127. data/vendor/libgit2/src/fileops.c +22 -1
  128. data/vendor/libgit2/src/fileops.h +8 -2
  129. data/vendor/libgit2/src/fnmatch.c +18 -5
  130. data/vendor/libgit2/src/global.c +21 -4
  131. data/vendor/libgit2/src/global.h +6 -0
  132. data/vendor/libgit2/src/graph.c +1 -1
  133. data/vendor/libgit2/src/index.c +159 -46
  134. data/vendor/libgit2/src/index.h +2 -0
  135. data/vendor/libgit2/src/iterator.c +1573 -1468
  136. data/vendor/libgit2/src/iterator.h +52 -69
  137. data/vendor/libgit2/src/merge.c +163 -64
  138. data/vendor/libgit2/src/merge.h +61 -2
  139. data/vendor/libgit2/src/merge_driver.c +397 -0
  140. data/vendor/libgit2/src/merge_driver.h +60 -0
  141. data/vendor/libgit2/src/merge_file.c +11 -49
  142. data/vendor/libgit2/src/netops.c +12 -10
  143. data/vendor/libgit2/src/object_api.c +19 -1
  144. data/vendor/libgit2/src/odb.c +228 -52
  145. data/vendor/libgit2/src/odb_loose.c +19 -1
  146. data/vendor/libgit2/src/odb_mempack.c +1 -1
  147. data/vendor/libgit2/src/odb_pack.c +27 -1
  148. data/vendor/libgit2/src/openssl_stream.c +4 -5
  149. data/vendor/libgit2/src/pack-objects.c +105 -76
  150. data/vendor/libgit2/src/pack-objects.h +13 -12
  151. data/vendor/libgit2/src/pack.c +16 -10
  152. data/vendor/libgit2/src/pack.h +2 -0
  153. data/vendor/libgit2/src/patch.c +216 -0
  154. data/vendor/libgit2/src/patch.h +66 -0
  155. data/vendor/libgit2/src/{diff_patch.c → patch_generate.c} +203 -376
  156. data/vendor/libgit2/src/patch_generate.h +68 -0
  157. data/vendor/libgit2/src/patch_parse.c +1159 -0
  158. data/vendor/libgit2/src/patch_parse.h +56 -0
  159. data/vendor/libgit2/src/path.c +38 -2
  160. data/vendor/libgit2/src/path.h +18 -0
  161. data/vendor/libgit2/src/pathspec.c +1 -1
  162. data/vendor/libgit2/src/pool.h +5 -0
  163. data/vendor/libgit2/src/pqueue.c +12 -5
  164. data/vendor/libgit2/src/pqueue.h +1 -0
  165. data/vendor/libgit2/src/proxy.c +32 -0
  166. data/vendor/libgit2/src/proxy.h +14 -0
  167. data/vendor/libgit2/src/push.c +1 -1
  168. data/vendor/libgit2/src/rebase.c +63 -36
  169. data/vendor/libgit2/src/refdb.c +4 -2
  170. data/vendor/libgit2/src/refdb_fs.c +82 -54
  171. data/vendor/libgit2/src/refs.c +13 -1
  172. data/vendor/libgit2/src/remote.c +20 -81
  173. data/vendor/libgit2/src/repository.c +212 -29
  174. data/vendor/libgit2/src/reset.c +1 -1
  175. data/vendor/libgit2/src/revparse.c +1 -1
  176. data/vendor/libgit2/src/revwalk.c +260 -184
  177. data/vendor/libgit2/src/settings.c +11 -3
  178. data/vendor/libgit2/src/signature.c +27 -2
  179. data/vendor/libgit2/src/sortedcache.c +14 -5
  180. data/vendor/libgit2/src/stash.c +1 -0
  181. data/vendor/libgit2/src/status.c +1 -0
  182. data/vendor/libgit2/src/stransport_stream.c +4 -2
  183. data/vendor/libgit2/src/stream.h +2 -2
  184. data/vendor/libgit2/src/submodule.c +16 -4
  185. data/vendor/libgit2/src/sysdir.c +1 -1
  186. data/vendor/libgit2/src/transport.c +3 -5
  187. data/vendor/libgit2/src/transports/http.c +38 -13
  188. data/vendor/libgit2/src/transports/local.c +4 -1
  189. data/vendor/libgit2/src/transports/smart.c +6 -0
  190. data/vendor/libgit2/src/transports/smart.h +1 -0
  191. data/vendor/libgit2/src/transports/smart_pkt.c +5 -13
  192. data/vendor/libgit2/src/transports/smart_protocol.c +22 -7
  193. data/vendor/libgit2/src/transports/winhttp.c +144 -11
  194. data/vendor/libgit2/src/tree.c +267 -2
  195. data/vendor/libgit2/src/unix/posix.h +10 -0
  196. data/vendor/libgit2/src/unix/pthread.h +2 -0
  197. data/vendor/libgit2/src/util.c +25 -2
  198. data/vendor/libgit2/src/util.h +10 -0
  199. data/vendor/libgit2/src/varint.c +44 -0
  200. data/vendor/libgit2/src/varint.h +15 -0
  201. data/vendor/libgit2/src/vector.c +58 -0
  202. data/vendor/libgit2/src/vector.h +8 -0
  203. data/vendor/libgit2/src/win32/posix.h +3 -0
  204. data/vendor/libgit2/src/win32/thread.c +18 -0
  205. data/vendor/libgit2/src/win32/thread.h +2 -0
  206. data/vendor/libgit2/src/win32/w32_util.h +1 -1
  207. data/vendor/libgit2/src/zstream.c +37 -8
  208. data/vendor/libgit2/src/zstream.h +8 -1
  209. metadata +100 -82
  210. data/vendor/libgit2/Makefile.embed +0 -60
  211. data/vendor/libgit2/src/delta-apply.c +0 -166
  212. data/vendor/libgit2/src/delta-apply.h +0 -62
  213. data/vendor/libgit2/src/diff_patch.h +0 -83
@@ -8,288 +8,372 @@
8
8
  #include "iterator.h"
9
9
  #include "tree.h"
10
10
  #include "index.h"
11
- #include "ignore.h"
12
- #include "buffer.h"
13
- #include "submodule.h"
14
- #include <ctype.h>
15
-
16
- #define ITERATOR_SET_CB(P,NAME_LC) do { \
17
- (P)->cb.current = NAME_LC ## _iterator__current; \
18
- (P)->cb.advance = NAME_LC ## _iterator__advance; \
19
- (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \
20
- (P)->cb.seek = NAME_LC ## _iterator__seek; \
21
- (P)->cb.reset = NAME_LC ## _iterator__reset; \
22
- (P)->cb.at_end = NAME_LC ## _iterator__at_end; \
23
- (P)->cb.free = NAME_LC ## _iterator__free; \
24
- } while (0)
25
-
26
- #define ITERATOR_CASE_FLAGS \
27
- (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE)
28
-
29
- #define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC,REPO) do { \
30
- (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \
31
- (P)->base.cb = &(P)->cb; \
32
- ITERATOR_SET_CB(P,NAME_LC); \
33
- (P)->base.repo = (REPO); \
34
- (P)->base.start = options && options->start ? \
35
- git__strdup(options->start) : NULL; \
36
- (P)->base.end = options && options->end ? \
37
- git__strdup(options->end) : NULL; \
38
- if ((options && options->start && !(P)->base.start) || \
39
- (options && options->end && !(P)->base.end)) { \
40
- git__free(P); return -1; } \
41
- (P)->base.strcomp = git__strcmp; \
42
- (P)->base.strncomp = git__strncmp; \
43
- (P)->base.prefixcomp = git__prefixcmp; \
44
- (P)->base.flags = options ? options->flags & ~ITERATOR_CASE_FLAGS : 0; \
45
- if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \
46
- (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \
47
- if (options && options->pathlist.count && \
48
- iterator_pathlist__init(&P->base, &options->pathlist) < 0) { \
49
- git__free(P); return -1; } \
50
- } while (0)
51
11
 
52
- #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
53
- #define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
54
- #define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
55
- #define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
56
- #define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
57
- #define iterator__include_conflicts(I) iterator__flag(I, INCLUDE_CONFLICTS)
12
+ #define GIT_ITERATOR_FIRST_ACCESS (1 << 15)
13
+ #define GIT_ITERATOR_HONOR_IGNORES (1 << 16)
14
+ #define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17)
58
15
 
59
- #define GIT_ITERATOR_FIRST_ACCESS (1 << 15)
16
+ #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
17
+ #define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
18
+ #define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
19
+ #define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
20
+ #define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
21
+ #define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS)
60
22
  #define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS)
23
+ #define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES)
24
+ #define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT)
61
25
 
62
- #define iterator__end(I) ((git_iterator *)(I))->end
63
- #define iterator__past_end(I,PATH) \
64
- (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0)
65
26
 
27
+ static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
28
+ {
29
+ if (ignore_case)
30
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
31
+ else
32
+ iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
33
+
34
+ iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp;
35
+ iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp;
36
+ iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp;
37
+ iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch;
66
38
 
67
- typedef enum {
68
- ITERATOR_PATHLIST_NONE = 0,
69
- ITERATOR_PATHLIST_MATCH = 1,
70
- ITERATOR_PATHLIST_MATCH_DIRECTORY = 2,
71
- ITERATOR_PATHLIST_MATCH_CHILD = 3,
72
- } iterator_pathlist__match_t;
39
+ git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp);
40
+ }
41
+
42
+ static int iterator_range_init(
43
+ git_iterator *iter, const char *start, const char *end)
44
+ {
45
+ if (start && *start) {
46
+ iter->start = git__strdup(start);
47
+ GITERR_CHECK_ALLOC(iter->start);
48
+
49
+ iter->start_len = strlen(iter->start);
50
+ }
51
+
52
+ if (end && *end) {
53
+ iter->end = git__strdup(end);
54
+ GITERR_CHECK_ALLOC(iter->end);
55
+
56
+ iter->end_len = strlen(iter->end);
57
+ }
58
+
59
+ iter->started = (iter->start == NULL);
60
+ iter->ended = false;
61
+
62
+ return 0;
63
+ }
64
+
65
+ static void iterator_range_free(git_iterator *iter)
66
+ {
67
+ if (iter->start) {
68
+ git__free(iter->start);
69
+ iter->start = NULL;
70
+ iter->start_len = 0;
71
+ }
72
+
73
+ if (iter->end) {
74
+ git__free(iter->end);
75
+ iter->end = NULL;
76
+ iter->end_len = 0;
77
+ }
78
+ }
79
+
80
+ static int iterator_reset_range(
81
+ git_iterator *iter, const char *start, const char *end)
82
+ {
83
+ iterator_range_free(iter);
84
+ return iterator_range_init(iter, start, end);
85
+ }
73
86
 
74
- static int iterator_pathlist__init(git_iterator *iter, git_strarray *pathspec)
87
+ static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist)
75
88
  {
76
89
  size_t i;
77
90
 
78
- if (git_vector_init(&iter->pathlist, pathspec->count,
79
- (git_vector_cmp)iter->strcomp) < 0)
91
+ if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0)
80
92
  return -1;
81
93
 
82
- for (i = 0; i < pathspec->count; i++) {
83
- if (!pathspec->strings[i])
94
+ for (i = 0; i < pathlist->count; i++) {
95
+ if (!pathlist->strings[i])
84
96
  continue;
85
97
 
86
- if (git_vector_insert(&iter->pathlist, pathspec->strings[i]) < 0)
98
+ if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0)
87
99
  return -1;
88
100
  }
89
101
 
90
- git_vector_sort(&iter->pathlist);
91
-
92
102
  return 0;
93
103
  }
94
104
 
95
- static iterator_pathlist__match_t iterator_pathlist__match(
96
- git_iterator *iter, const char *path, size_t path_len)
105
+ static int iterator_init_common(
106
+ git_iterator *iter,
107
+ git_repository *repo,
108
+ git_index *index,
109
+ git_iterator_options *given_opts)
97
110
  {
98
- const char *p;
99
- size_t idx;
111
+ static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT;
112
+ git_iterator_options *options = given_opts ? given_opts : &default_opts;
113
+ bool ignore_case;
114
+ int precompose;
100
115
  int error;
101
116
 
102
- error = git_vector_bsearch2(&idx, &iter->pathlist,
103
- (git_vector_cmp)iter->strcomp, path);
117
+ iter->repo = repo;
118
+ iter->index = index;
119
+ iter->flags = options->flags;
104
120
 
105
- if (error == 0)
106
- return ITERATOR_PATHLIST_MATCH;
121
+ if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) {
122
+ ignore_case = true;
123
+ } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) {
124
+ ignore_case = false;
125
+ } else if (repo) {
126
+ git_index *index;
107
127
 
108
- /* at this point, the path we're examining may be a directory (though we
109
- * don't know that yet, since we're avoiding a stat unless it's necessary)
110
- * so see if the pathlist contains a file beneath this directory.
111
- */
112
- while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) {
113
- if (iter->prefixcomp(p, path) != 0)
114
- break;
128
+ if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0)
129
+ return error;
115
130
 
116
- /* an exact match would have been matched by the bsearch above */
117
- assert(p[path_len]);
131
+ ignore_case = !!index->ignore_case;
118
132
 
119
- /* is this a literal directory entry (eg `foo/`) or a file beneath */
120
- if (p[path_len] == '/') {
121
- return (p[path_len+1] == '\0') ?
122
- ITERATOR_PATHLIST_MATCH_DIRECTORY :
123
- ITERATOR_PATHLIST_MATCH_CHILD;
124
- }
133
+ if (ignore_case == 1)
134
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
135
+ else
136
+ iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE;
137
+ } else {
138
+ ignore_case = false;
139
+ }
125
140
 
126
- if (p[path_len] > '/')
127
- break;
141
+ /* try to look up precompose and set flag if appropriate */
142
+ if (repo &&
143
+ (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 &&
144
+ (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) {
128
145
 
129
- idx++;
146
+ if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
147
+ giterr_clear();
148
+ else if (precompose)
149
+ iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
130
150
  }
131
151
 
132
- return ITERATOR_PATHLIST_NONE;
152
+ if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND))
153
+ iter->flags |= GIT_ITERATOR_INCLUDE_TREES;
154
+
155
+ if ((error = iterator_range_init(iter, options->start, options->end)) < 0 ||
156
+ (error = iterator_pathlist_init(iter, &options->pathlist)) < 0)
157
+ return error;
158
+
159
+ iterator_set_ignore_case(iter, ignore_case);
160
+ return 0;
133
161
  }
134
162
 
135
- static void iterator_pathlist_walk__reset(git_iterator *iter)
163
+ static void iterator_clear(git_iterator *iter)
136
164
  {
165
+ iter->started = false;
166
+ iter->ended = false;
167
+ iter->stat_calls = 0;
137
168
  iter->pathlist_walk_idx = 0;
169
+ iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS;
170
+ }
171
+
172
+ GIT_INLINE(bool) iterator_has_started(
173
+ git_iterator *iter, const char *path, bool is_submodule)
174
+ {
175
+ size_t path_len;
176
+
177
+ if (iter->start == NULL || iter->started == true)
178
+ return true;
179
+
180
+ /* the starting path is generally a prefix - we have started once we
181
+ * are prefixed by this path
182
+ */
183
+ iter->started = (iter->prefixcomp(path, iter->start) >= 0);
184
+
185
+ if (iter->started)
186
+ return true;
187
+
188
+ path_len = strlen(path);
189
+
190
+ /* if, however, we are a submodule, then we support `start` being
191
+ * suffixed with a `/` for crazy legacy reasons. match `submod`
192
+ * with a start path of `submod/`.
193
+ */
194
+ if (is_submodule && iter->start_len && path_len == iter->start_len - 1 &&
195
+ iter->start[iter->start_len-1] == '/')
196
+ return true;
197
+
198
+ /* if, however, our current path is a directory, and our starting path
199
+ * is _beneath_ that directory, then recurse into the directory (even
200
+ * though we have not yet "started")
201
+ */
202
+ if (path_len > 0 && path[path_len-1] == '/' &&
203
+ iter->strncomp(path, iter->start, path_len) == 0)
204
+ return true;
205
+
206
+ return false;
207
+ }
208
+
209
+ GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path)
210
+ {
211
+ if (iter->end == NULL)
212
+ return false;
213
+ else if (iter->ended)
214
+ return true;
215
+
216
+ iter->ended = (iter->prefixcomp(path, iter->end) > 0);
217
+ return iter->ended;
138
218
  }
139
219
 
140
- /* walker for the index iterator that allows it to walk the sorted pathlist
141
- * entries alongside the sorted index entries. the `iter->pathlist_walk_idx`
142
- * stores the starting position for subsequent calls, the position is advanced
143
- * along with the index iterator, with a special case for handling directories
144
- * in the pathlist that are specified without trailing '/'. (eg, `foo`).
145
- * we do not advance over these entries until we're certain that the index
146
- * iterator will not ask us for a file beneath that directory (eg, `foo/bar`).
220
+ /* walker for the index and tree iterator that allows it to walk the sorted
221
+ * pathlist entries alongside sorted iterator entries.
147
222
  */
148
- static bool iterator_pathlist_walk__contains(git_iterator *iter, const char *path)
223
+ static bool iterator_pathlist_next_is(git_iterator *iter, const char *path)
149
224
  {
150
- size_t i;
151
225
  char *p;
152
- size_t p_len;
226
+ size_t path_len, p_len, cmp_len, i;
153
227
  int cmp;
154
228
 
229
+ if (iter->pathlist.length == 0)
230
+ return true;
231
+
232
+ git_vector_sort(&iter->pathlist);
233
+
234
+ path_len = strlen(path);
235
+
236
+ /* for comparison, drop the trailing slash on the current '/' */
237
+ if (path_len && path[path_len-1] == '/')
238
+ path_len--;
239
+
155
240
  for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) {
156
241
  p = iter->pathlist.contents[i];
157
242
  p_len = strlen(p);
158
243
 
244
+ if (p_len && p[p_len-1] == '/')
245
+ p_len--;
246
+
247
+ cmp_len = min(path_len, p_len);
248
+
159
249
  /* see if the pathlist entry is a prefix of this path */
160
- cmp = iter->strncomp(p, path, p_len);
250
+ cmp = iter->strncomp(p, path, cmp_len);
251
+
252
+ /* prefix match - see if there's an exact match, or if we were
253
+ * given a path that matches the directory
254
+ */
255
+ if (cmp == 0) {
256
+ /* if this pathlist entry is not suffixed with a '/' then
257
+ * it matches a path that is a file or a directory.
258
+ * (eg, pathlist = "foo" and path is "foo" or "foo/" or
259
+ * "foo/something")
260
+ */
261
+ if (p[cmp_len] == '\0' &&
262
+ (path[cmp_len] == '\0' || path[cmp_len] == '/'))
263
+ return true;
264
+
265
+ /* if this pathlist entry _is_ suffixed with a '/' then
266
+ * it matches only paths that are directories.
267
+ * (eg, pathlist = "foo/" and path is "foo/" or "foo/something")
268
+ */
269
+ if (p[cmp_len] == '/' && path[cmp_len] == '/')
270
+ return true;
271
+ }
161
272
 
162
273
  /* this pathlist entry sorts before the given path, try the next */
163
- if (!p_len || cmp < 0)
274
+ else if (cmp < 0) {
164
275
  iter->pathlist_walk_idx++;
276
+ continue;
277
+ }
165
278
 
166
279
  /* this pathlist sorts after the given path, no match. */
167
- else if (cmp > 0)
168
- return false;
169
-
170
- /* match! an exact match (`foo` vs `foo`), the path is a child of an
171
- * explicit directory in the pathlist (`foo/` vs `foo/bar`) or the path
172
- * is a child of an entry in the pathlist (`foo` vs `foo/bar`)
173
- */
174
- else if (path[p_len] == '\0' || p[p_len - 1] == '/' || path[p_len] == '/')
175
- return true;
176
-
177
- /* only advance the start index for future callers if we know that we
178
- * will not see a child of this path. eg, a pathlist entry `foo` is
179
- * a prefix for `foo.txt` and `foo/bar`. don't advance the start
180
- * pathlist index when we see `foo.txt` or we would miss a subsequent
181
- * inspection of `foo/bar`. only advance when there are no more
182
- * potential children.
183
- */
184
- else if (path[p_len] > '/')
185
- iter->pathlist_walk_idx++;
280
+ else if (cmp > 0) {
281
+ break;
282
+ }
186
283
  }
187
284
 
188
285
  return false;
189
286
  }
190
287
 
191
- static void iterator_pathlist__update_ignore_case(git_iterator *iter)
192
- {
193
- git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp);
194
- git_vector_sort(&iter->pathlist);
195
-
196
- iter->pathlist_walk_idx = 0;
197
- }
198
-
288
+ typedef enum {
289
+ ITERATOR_PATHLIST_NONE = 0,
290
+ ITERATOR_PATHLIST_IS_FILE = 1,
291
+ ITERATOR_PATHLIST_IS_DIR = 2,
292
+ ITERATOR_PATHLIST_IS_PARENT = 3,
293
+ ITERATOR_PATHLIST_FULL = 4,
294
+ } iterator_pathlist_search_t;
199
295
 
200
- static int iterator__reset_range(
201
- git_iterator *iter, const char *start, const char *end)
296
+ static iterator_pathlist_search_t iterator_pathlist_search(
297
+ git_iterator *iter, const char *path, size_t path_len)
202
298
  {
203
- if (start) {
204
- if (iter->start)
205
- git__free(iter->start);
206
- iter->start = git__strdup(start);
207
- GITERR_CHECK_ALLOC(iter->start);
208
- }
209
-
210
- if (end) {
211
- if (iter->end)
212
- git__free(iter->end);
213
- iter->end = git__strdup(end);
214
- GITERR_CHECK_ALLOC(iter->end);
215
- }
216
-
217
- iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS;
299
+ const char *p;
300
+ size_t idx;
301
+ int error;
218
302
 
219
- return 0;
220
- }
303
+ if (iter->pathlist.length == 0)
304
+ return ITERATOR_PATHLIST_FULL;
221
305
 
222
- static int iterator__update_ignore_case(
223
- git_iterator *iter,
224
- git_iterator_flag_t flags)
225
- {
226
- bool ignore_case;
227
- int error;
306
+ git_vector_sort(&iter->pathlist);
228
307
 
229
- if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
230
- ignore_case = true;
231
- else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0)
232
- ignore_case = false;
233
- else {
234
- git_index *index;
308
+ error = git_vector_bsearch2(&idx, &iter->pathlist,
309
+ (git_vector_cmp)iter->strcomp, path);
235
310
 
236
- if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0)
237
- return error;
311
+ /* the given path was found in the pathlist. since the pathlist only
312
+ * matches directories when they're suffixed with a '/', analyze the
313
+ * path string to determine whether it's a directory or not.
314
+ */
315
+ if (error == 0) {
316
+ if (path_len && path[path_len-1] == '/')
317
+ return ITERATOR_PATHLIST_IS_DIR;
238
318
 
239
- ignore_case = (index->ignore_case == 1);
319
+ return ITERATOR_PATHLIST_IS_FILE;
240
320
  }
241
321
 
242
- if (ignore_case) {
243
- iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE);
322
+ /* at this point, the path we're examining may be a directory (though we
323
+ * don't know that yet, since we're avoiding a stat unless it's necessary)
324
+ * so walk the pathlist looking for the given path with a '/' after it,
325
+ */
326
+ while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) {
327
+ if (iter->prefixcomp(p, path) != 0)
328
+ break;
244
329
 
245
- iter->strcomp = git__strcasecmp;
246
- iter->strncomp = git__strncasecmp;
247
- iter->prefixcomp = git__prefixcmp_icase;
248
- } else {
249
- iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);
330
+ /* an exact match would have been matched by the bsearch above */
331
+ assert(p[path_len]);
250
332
 
251
- iter->strcomp = git__strcmp;
252
- iter->strncomp = git__strncmp;
253
- iter->prefixcomp = git__prefixcmp;
254
- }
333
+ /* is this a literal directory entry (eg `foo/`) or a file beneath */
334
+ if (p[path_len] == '/') {
335
+ return (p[path_len+1] == '\0') ?
336
+ ITERATOR_PATHLIST_IS_DIR :
337
+ ITERATOR_PATHLIST_IS_PARENT;
338
+ }
255
339
 
256
- iterator_pathlist__update_ignore_case(iter);
340
+ if (p[path_len] > '/')
341
+ break;
257
342
 
258
- return 0;
259
- }
343
+ idx++;
344
+ }
260
345
 
261
- GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry)
262
- {
263
- if (entry) *entry = NULL;
346
+ return ITERATOR_PATHLIST_NONE;
264
347
  }
265
348
 
349
+ /* Empty iterator */
266
350
 
267
- static int empty_iterator__noop(const git_index_entry **e, git_iterator *i)
351
+ static int empty_iterator_noop(const git_index_entry **e, git_iterator *i)
268
352
  {
269
353
  GIT_UNUSED(i);
270
- iterator__clear_entry(e);
271
- return GIT_ITEROVER;
272
- }
273
354
 
274
- static int empty_iterator__seek(git_iterator *i, const char *p)
275
- {
276
- GIT_UNUSED(i); GIT_UNUSED(p);
277
- return -1;
355
+ if (e)
356
+ *e = NULL;
357
+
358
+ return GIT_ITEROVER;
278
359
  }
279
360
 
280
- static int empty_iterator__reset(git_iterator *i, const char *s, const char *e)
361
+ static int empty_iterator_advance_over(
362
+ const git_index_entry **e,
363
+ git_iterator_status_t *s,
364
+ git_iterator *i)
281
365
  {
282
- GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e);
283
- return 0;
366
+ *s = GIT_ITERATOR_STATUS_EMPTY;
367
+ return empty_iterator_noop(e, i);
284
368
  }
285
369
 
286
- static int empty_iterator__at_end(git_iterator *i)
370
+ static int empty_iterator_reset(git_iterator *i)
287
371
  {
288
372
  GIT_UNUSED(i);
289
- return 1;
373
+ return 0;
290
374
  }
291
375
 
292
- static void empty_iterator__free(git_iterator *i)
376
+ static void empty_iterator_free(git_iterator *i)
293
377
  {
294
378
  GIT_UNUSED(i);
295
379
  }
@@ -300,1516 +384,1510 @@ typedef struct {
300
384
  } empty_iterator;
301
385
 
302
386
  int git_iterator_for_nothing(
303
- git_iterator **iter,
387
+ git_iterator **out,
304
388
  git_iterator_options *options)
305
389
  {
306
- empty_iterator *i = git__calloc(1, sizeof(empty_iterator));
307
- GITERR_CHECK_ALLOC(i);
390
+ empty_iterator *iter;
308
391
 
309
- #define empty_iterator__current empty_iterator__noop
310
- #define empty_iterator__advance empty_iterator__noop
311
- #define empty_iterator__advance_into empty_iterator__noop
392
+ static git_iterator_callbacks callbacks = {
393
+ empty_iterator_noop,
394
+ empty_iterator_noop,
395
+ empty_iterator_noop,
396
+ empty_iterator_advance_over,
397
+ empty_iterator_reset,
398
+ empty_iterator_free
399
+ };
312
400
 
313
- ITERATOR_BASE_INIT(i, empty, EMPTY, NULL);
401
+ *out = NULL;
314
402
 
315
- if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0)
316
- i->base.flags |= GIT_ITERATOR_IGNORE_CASE;
403
+ iter = git__calloc(1, sizeof(empty_iterator));
404
+ GITERR_CHECK_ALLOC(iter);
317
405
 
318
- *iter = (git_iterator *)i;
406
+ iter->base.type = GIT_ITERATOR_TYPE_EMPTY;
407
+ iter->base.cb = &callbacks;
408
+ iter->base.flags = options->flags;
409
+
410
+ *out = &iter->base;
319
411
  return 0;
320
412
  }
321
413
 
414
+ /* Tree iterator */
415
+
416
+ typedef struct {
417
+ git_tree_entry *tree_entry;
418
+ const char *parent_path;
419
+ } tree_iterator_entry;
322
420
 
323
- typedef struct tree_iterator_entry tree_iterator_entry;
324
- struct tree_iterator_entry {
325
- tree_iterator_entry *parent;
326
- const git_tree_entry *te;
421
+ typedef struct {
327
422
  git_tree *tree;
328
- };
329
423
 
330
- typedef struct tree_iterator_frame tree_iterator_frame;
331
- struct tree_iterator_frame {
332
- tree_iterator_frame *up, *down;
424
+ /* path to this particular frame (folder) */
425
+ git_buf path;
333
426
 
334
- size_t n_entries; /* items in this frame */
335
- size_t current; /* start of currently active range in frame */
336
- size_t next; /* start of next range in frame */
427
+ /* a sorted list of the entries for this frame (folder), these are
428
+ * actually pointers to the iterator's entry pool.
429
+ */
430
+ git_vector entries;
431
+ tree_iterator_entry *current;
337
432
 
338
- const char *start;
339
- size_t startlen;
433
+ size_t next_idx;
340
434
 
341
- tree_iterator_entry *entries[GIT_FLEX_ARRAY];
342
- };
435
+ /* on case insensitive iterations, we also have an array of other
436
+ * paths that were case insensitively equal to this one, and their
437
+ * tree objects. we have coalesced the tree entries into this frame.
438
+ * a child `tree_iterator_entry` will contain a pointer to its actual
439
+ * parent path.
440
+ */
441
+ git_vector similar_trees;
442
+ git_array_t(git_buf) similar_paths;
443
+ } tree_iterator_frame;
343
444
 
344
445
  typedef struct {
345
446
  git_iterator base;
346
- git_iterator_callbacks cb;
347
- tree_iterator_frame *head, *root;
348
- git_pool pool;
447
+ git_tree *root;
448
+ git_array_t(tree_iterator_frame) frames;
449
+
349
450
  git_index_entry entry;
350
- git_buf path;
351
- int path_ambiguities;
352
- bool path_has_filename;
353
- bool entry_is_current;
451
+ git_buf entry_path;
452
+
453
+ /* a pool of entries to reduce the number of allocations */
454
+ git_pool entry_pool;
354
455
  } tree_iterator;
355
456
 
356
- static char *tree_iterator__current_filename(
357
- tree_iterator *ti, const git_tree_entry *te)
457
+ GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame(
458
+ tree_iterator *iter)
358
459
  {
359
- if (!ti->path_has_filename) {
360
- if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
361
- return NULL;
362
-
363
- if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0)
364
- return NULL;
365
-
366
- ti->path_has_filename = true;
367
- }
368
-
369
- return ti->path.ptr;
460
+ return iter->frames.size > 1 ?
461
+ &iter->frames.ptr[iter->frames.size-2] : NULL;
370
462
  }
371
463
 
372
- static void tree_iterator__rewrite_filename(tree_iterator *ti)
464
+ GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame(
465
+ tree_iterator *iter)
373
466
  {
374
- tree_iterator_entry *scan = ti->head->entries[ti->head->current];
375
- ssize_t strpos = ti->path.size;
376
- const git_tree_entry *te;
377
-
378
- if (strpos && ti->path.ptr[strpos - 1] == '/')
379
- strpos--;
380
-
381
- for (; scan && (te = scan->te); scan = scan->parent) {
382
- strpos -= te->filename_len;
383
- memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len);
384
- strpos -= 1; /* separator */
385
- }
467
+ return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL;
386
468
  }
387
469
 
388
- static int tree_iterator__te_cmp(
389
- const git_tree_entry *a,
390
- const git_tree_entry *b,
391
- int (*compare)(const char *, const char *, size_t))
470
+ GIT_INLINE(int) tree_entry_cmp(
471
+ const git_tree_entry *a, const git_tree_entry *b, bool icase)
392
472
  {
393
473
  return git_path_cmp(
394
474
  a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE,
395
475
  b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE,
396
- compare);
476
+ icase ? git__strncasecmp : git__strncmp);
397
477
  }
398
478
 
399
- static int tree_iterator__ci_cmp(const void *a, const void *b, void *p)
479
+ GIT_INLINE(int) tree_iterator_entry_cmp(const void *ptr_a, const void *ptr_b)
400
480
  {
401
- const tree_iterator_entry *ae = a, *be = b;
402
- int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp);
481
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
482
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
403
483
 
404
- if (!cmp) {
405
- /* stabilize sort order among equivalent names */
406
- if (!ae->parent->te || !be->parent->te)
407
- cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp);
408
- else
409
- cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p);
410
- }
484
+ return tree_entry_cmp(a->tree_entry, b->tree_entry, false);
485
+ }
411
486
 
412
- return cmp;
487
+ GIT_INLINE(int) tree_iterator_entry_cmp_icase(
488
+ const void *ptr_a, const void *ptr_b)
489
+ {
490
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
491
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
492
+
493
+ return tree_entry_cmp(a->tree_entry, b->tree_entry, true);
413
494
  }
414
495
 
415
- static int tree_iterator__search_cmp(const void *key, const void *val, void *p)
496
+ static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b)
416
497
  {
417
- const tree_iterator_frame *tf = key;
418
- const git_tree_entry *te = ((tree_iterator_entry *)val)->te;
498
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
499
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
419
500
 
420
- return git_path_cmp(
421
- tf->start, tf->startlen, false,
422
- te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE,
423
- ((git_iterator *)p)->strncomp);
501
+ int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true);
502
+
503
+ /* stabilize the sort order for filenames that are (case insensitively)
504
+ * the same by examining the parent path (case sensitively) before
505
+ * falling back to a case sensitive sort of the filename.
506
+ */
507
+ if (!c && a->parent_path != b->parent_path)
508
+ c = git__strcmp(a->parent_path, b->parent_path);
509
+
510
+ if (!c)
511
+ c = tree_entry_cmp(a->tree_entry, b->tree_entry, false);
512
+
513
+ return c;
424
514
  }
425
515
 
426
- static bool tree_iterator__move_to_next(
427
- tree_iterator *ti, tree_iterator_frame *tf)
516
+ static int tree_iterator_compute_path(
517
+ git_buf *out,
518
+ tree_iterator_entry *entry)
428
519
  {
429
- if (tf->next > tf->current + 1)
430
- ti->path_ambiguities--;
520
+ git_buf_clear(out);
431
521
 
432
- if (!tf->up) { /* at root */
433
- tf->current = tf->next;
434
- return false;
435
- }
522
+ if (entry->parent_path)
523
+ git_buf_joinpath(out, entry->parent_path, entry->tree_entry->filename);
524
+ else
525
+ git_buf_puts(out, entry->tree_entry->filename);
436
526
 
437
- for (; tf->current < tf->next; tf->current++) {
438
- git_tree_free(tf->entries[tf->current]->tree);
439
- tf->entries[tf->current]->tree = NULL;
440
- }
527
+ if (git_tree_entry__is_tree(entry->tree_entry))
528
+ git_buf_putc(out, '/');
529
+
530
+ if (git_buf_oom(out))
531
+ return -1;
441
532
 
442
- return (tf->current < tf->n_entries);
533
+ return 0;
443
534
  }
444
535
 
445
- static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
536
+ static int tree_iterator_frame_init(
537
+ tree_iterator *iter,
538
+ git_tree *tree,
539
+ tree_iterator_entry *frame_entry)
446
540
  {
541
+ tree_iterator_frame *new_frame = NULL;
542
+ tree_iterator_entry *new_entry;
543
+ git_tree *dup = NULL;
544
+ git_tree_entry *tree_entry;
545
+ git_vector_cmp cmp;
546
+ size_t i;
447
547
  int error = 0;
448
- const git_tree_entry *te, *last = NULL;
449
548
 
450
- tf->next = tf->current;
549
+ new_frame = git_array_alloc(iter->frames);
550
+ GITERR_CHECK_ALLOC(new_frame);
451
551
 
452
- for (; tf->next < tf->n_entries; tf->next++, last = te) {
453
- te = tf->entries[tf->next]->te;
552
+ memset(new_frame, 0, sizeof(tree_iterator_frame));
454
553
 
455
- if (last && tree_iterator__te_cmp(last, te, ti->base.strncomp))
456
- break;
554
+ if ((error = git_tree_dup(&dup, tree)) < 0)
555
+ goto done;
556
+
557
+ memset(new_frame, 0x0, sizeof(tree_iterator_frame));
558
+ new_frame->tree = dup;
559
+
560
+ if (frame_entry &&
561
+ (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0)
562
+ goto done;
563
+
564
+ cmp = iterator__ignore_case(&iter->base) ?
565
+ tree_iterator_entry_sort_icase : NULL;
566
+
567
+ if ((error = git_vector_init(
568
+ &new_frame->entries, dup->entries.size, cmp)) < 0)
569
+ goto done;
457
570
 
458
- /* try to load trees for items in [current,next) range */
459
- if (!error && git_tree_entry__is_tree(te))
460
- error = git_tree_lookup(
461
- &tf->entries[tf->next]->tree, ti->base.repo, te->oid);
571
+ git_array_foreach(dup->entries, i, tree_entry) {
572
+ new_entry = git_pool_malloc(&iter->entry_pool, 1);
573
+ GITERR_CHECK_ALLOC(new_entry);
574
+
575
+ new_entry->tree_entry = tree_entry;
576
+ new_entry->parent_path = new_frame->path.ptr;
577
+
578
+ if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0)
579
+ goto done;
462
580
  }
463
581
 
464
- if (tf->next > tf->current + 1)
465
- ti->path_ambiguities++;
582
+ git_vector_set_sorted(&new_frame->entries,
583
+ !iterator__ignore_case(&iter->base));
466
584
 
467
- /* if a tree lookup failed, advance over this span and return failure */
585
+ done:
468
586
  if (error < 0) {
469
- tree_iterator__move_to_next(ti, tf);
470
- return error;
587
+ git_tree_free(dup);
588
+ git_array_pop(iter->frames);
471
589
  }
472
590
 
473
- if (last && !tree_iterator__current_filename(ti, last))
474
- return -1; /* must have been allocation failure */
475
-
476
- return 0;
591
+ return error;
477
592
  }
478
593
 
479
- GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti)
594
+ GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry(
595
+ tree_iterator_frame *frame)
480
596
  {
481
- return (ti->head->current < ti->head->n_entries &&
482
- ti->head->entries[ti->head->current]->tree != NULL);
597
+ return frame->current;
483
598
  }
484
599
 
485
- static int tree_iterator__push_frame(tree_iterator *ti)
600
+ GIT_INLINE(int) tree_iterator_frame_push_neighbors(
601
+ tree_iterator *iter,
602
+ tree_iterator_frame *parent_frame,
603
+ tree_iterator_frame *frame,
604
+ const char *filename)
486
605
  {
606
+ tree_iterator_entry *entry, *new_entry;
607
+ git_tree *tree = NULL;
608
+ git_tree_entry *tree_entry;
609
+ git_buf *path;
610
+ size_t new_size, i;
487
611
  int error = 0;
488
- tree_iterator_frame *head = ti->head, *tf = NULL;
489
- size_t i, n_entries = 0, alloclen;
490
612
 
491
- if (head->current >= head->n_entries || !head->entries[head->current]->tree)
492
- return GIT_ITEROVER;
493
-
494
- for (i = head->current; i < head->next; ++i)
495
- n_entries += git_tree_entrycount(head->entries[i]->tree);
613
+ while (parent_frame->next_idx < parent_frame->entries.length) {
614
+ entry = parent_frame->entries.contents[parent_frame->next_idx];
496
615
 
497
- GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, sizeof(tree_iterator_entry *), n_entries);
498
- GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, sizeof(tree_iterator_frame));
616
+ if (strcasecmp(filename, entry->tree_entry->filename) != 0)
617
+ break;
499
618
 
500
- tf = git__calloc(1, alloclen);
501
- GITERR_CHECK_ALLOC(tf);
619
+ if ((error = git_tree_lookup(&tree,
620
+ iter->base.repo, entry->tree_entry->oid)) < 0)
621
+ break;
502
622
 
503
- tf->n_entries = n_entries;
623
+ if (git_vector_insert(&parent_frame->similar_trees, tree) < 0)
624
+ break;
504
625
 
505
- tf->up = head;
506
- head->down = tf;
507
- ti->head = tf;
626
+ path = git_array_alloc(parent_frame->similar_paths);
627
+ GITERR_CHECK_ALLOC(path);
508
628
 
509
- for (i = head->current, n_entries = 0; i < head->next; ++i) {
510
- git_tree *tree = head->entries[i]->tree;
511
- size_t j, max_j = git_tree_entrycount(tree);
629
+ memset(path, 0, sizeof(git_buf));
512
630
 
513
- for (j = 0; j < max_j; ++j) {
514
- tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1);
515
- GITERR_CHECK_ALLOC(entry);
631
+ if ((error = tree_iterator_compute_path(path, entry)) < 0)
632
+ break;
516
633
 
517
- entry->parent = head->entries[i];
518
- entry->te = git_tree_entry_byindex(tree, j);
519
- entry->tree = NULL;
634
+ GITERR_CHECK_ALLOC_ADD(&new_size,
635
+ frame->entries.length, tree->entries.size);
636
+ git_vector_size_hint(&frame->entries, new_size);
520
637
 
521
- tf->entries[n_entries++] = entry;
522
- }
523
- }
638
+ git_array_foreach(tree->entries, i, tree_entry) {
639
+ new_entry = git_pool_malloc(&iter->entry_pool, 1);
640
+ GITERR_CHECK_ALLOC(new_entry);
524
641
 
525
- /* if ignore_case, sort entries case insensitively */
526
- if (iterator__ignore_case(ti))
527
- git__tsort_r(
528
- (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf);
642
+ new_entry->tree_entry = tree_entry;
643
+ new_entry->parent_path = path->ptr;
529
644
 
530
- /* pick tf->current based on "start" (or start at zero) */
531
- if (head->startlen > 0) {
532
- git__bsearch_r((void **)tf->entries, tf->n_entries, head,
533
- tree_iterator__search_cmp, ti, &tf->current);
645
+ if ((error = git_vector_insert(&frame->entries, new_entry)) < 0)
646
+ break;
647
+ }
534
648
 
535
- while (tf->current &&
536
- !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti))
537
- tf->current--;
649
+ if (error)
650
+ break;
538
651
 
539
- if ((tf->start = strchr(head->start, '/')) != NULL) {
540
- tf->start++;
541
- tf->startlen = strlen(tf->start);
542
- }
652
+ parent_frame->next_idx++;
543
653
  }
544
654
 
545
- ti->path_has_filename = ti->entry_is_current = false;
655
+ return error;
656
+ }
546
657
 
547
- if ((error = tree_iterator__set_next(ti, tf)) < 0)
548
- return error;
658
+ GIT_INLINE(int) tree_iterator_frame_push(
659
+ tree_iterator *iter, tree_iterator_entry *entry)
660
+ {
661
+ tree_iterator_frame *parent_frame, *frame;
662
+ git_tree *tree = NULL;
663
+ int error;
549
664
 
550
- /* autoexpand as needed */
551
- if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
552
- return tree_iterator__push_frame(ti);
665
+ if ((error = git_tree_lookup(&tree,
666
+ iter->base.repo, entry->tree_entry->oid)) < 0 ||
667
+ (error = tree_iterator_frame_init(iter, tree, entry)) < 0)
668
+ goto done;
553
669
 
554
- return 0;
555
- }
670
+ parent_frame = tree_iterator_parent_frame(iter);
671
+ frame = tree_iterator_current_frame(iter);
556
672
 
557
- static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)
558
- {
559
- tree_iterator_frame *tf = ti->head;
673
+ /* if we're case insensitive, then we may have another directory that
674
+ * is (case insensitively) equal to this one. coalesce those children
675
+ * into this tree.
676
+ */
677
+ if (iterator__ignore_case(&iter->base))
678
+ error = tree_iterator_frame_push_neighbors(iter,
679
+ parent_frame, frame, entry->tree_entry->filename);
560
680
 
561
- assert(tf);
681
+ done:
682
+ git_tree_free(tree);
683
+ return error;
684
+ }
562
685
 
563
- if (!tf->up)
564
- return false;
686
+ static void tree_iterator_frame_pop(tree_iterator *iter)
687
+ {
688
+ tree_iterator_frame *frame;
689
+ git_buf *buf = NULL;
690
+ git_tree *tree;
691
+ size_t i;
565
692
 
566
- ti->head = tf->up;
567
- ti->head->down = NULL;
693
+ assert(iter->frames.size);
568
694
 
569
- tree_iterator__move_to_next(ti, tf);
695
+ frame = git_array_pop(iter->frames);
570
696
 
571
- if (!final) { /* if final, don't bother to clean up */
572
- // TODO: maybe free the pool so far?
573
- git_buf_rtruncate_at_char(&ti->path, '/');
574
- }
697
+ git_vector_free(&frame->entries);
698
+ git_tree_free(frame->tree);
575
699
 
576
- git__free(tf);
700
+ do {
701
+ buf = git_array_pop(frame->similar_paths);
702
+ git_buf_free(buf);
703
+ } while (buf != NULL);
577
704
 
578
- return true;
579
- }
705
+ git_array_clear(frame->similar_paths);
580
706
 
581
- static void tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
582
- {
583
- while (tree_iterator__pop_frame(ti, final)) /* pop to root */;
707
+ git_vector_foreach(&frame->similar_trees, i, tree)
708
+ git_tree_free(tree);
584
709
 
585
- if (!final) {
586
- assert(ti->head);
710
+ git_vector_free(&frame->similar_trees);
587
711
 
588
- ti->head->current = to_end ? ti->head->n_entries : 0;
589
- ti->path_ambiguities = 0;
590
- git_buf_clear(&ti->path);
591
- }
712
+ git_buf_free(&frame->path);
592
713
  }
593
714
 
594
- static int tree_iterator__update_entry(tree_iterator *ti)
715
+ static int tree_iterator_current(
716
+ const git_index_entry **out, git_iterator *i)
595
717
  {
596
- tree_iterator_frame *tf;
597
- const git_tree_entry *te;
598
-
599
- if (ti->entry_is_current)
600
- return 0;
601
-
602
- tf = ti->head;
603
- te = tf->entries[tf->current]->te;
718
+ tree_iterator *iter = (tree_iterator *)i;
604
719
 
605
- ti->entry.mode = te->attr;
606
- git_oid_cpy(&ti->entry.id, te->oid);
720
+ if (!iterator__has_been_accessed(i))
721
+ return iter->base.cb->advance(out, i);
607
722
 
608
- ti->entry.path = tree_iterator__current_filename(ti, te);
609
- GITERR_CHECK_ALLOC(ti->entry.path);
610
-
611
- if (ti->path_ambiguities > 0)
612
- tree_iterator__rewrite_filename(ti);
613
-
614
- if (iterator__past_end(ti, ti->entry.path)) {
615
- tree_iterator__pop_all(ti, true, false);
723
+ if (!iter->frames.size) {
724
+ *out = NULL;
616
725
  return GIT_ITEROVER;
617
726
  }
618
727
 
619
- ti->entry_is_current = true;
620
-
728
+ *out = &iter->entry;
621
729
  return 0;
622
730
  }
623
731
 
624
- static int tree_iterator__current_internal(
625
- const git_index_entry **entry, git_iterator *self)
732
+ static void tree_iterator_set_current(
733
+ tree_iterator *iter,
734
+ tree_iterator_frame *frame,
735
+ tree_iterator_entry *entry)
626
736
  {
627
- int error;
628
- tree_iterator *ti = (tree_iterator *)self;
629
- tree_iterator_frame *tf = ti->head;
630
-
631
- iterator__clear_entry(entry);
632
-
633
- if (tf->current >= tf->n_entries)
634
- return GIT_ITEROVER;
737
+ git_tree_entry *tree_entry = entry->tree_entry;
635
738
 
636
- if ((error = tree_iterator__update_entry(ti)) < 0)
637
- return error;
638
-
639
- if (entry)
640
- *entry = &ti->entry;
739
+ frame->current = entry;
641
740
 
642
- ti->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
741
+ memset(&iter->entry, 0x0, sizeof(git_index_entry));
643
742
 
644
- return 0;
743
+ iter->entry.mode = tree_entry->attr;
744
+ iter->entry.path = iter->entry_path.ptr;
745
+ git_oid_cpy(&iter->entry.id, tree_entry->oid);
645
746
  }
646
747
 
647
- static int tree_iterator__advance_into_internal(git_iterator *self)
748
+ static int tree_iterator_advance(const git_index_entry **out, git_iterator *i)
648
749
  {
750
+ tree_iterator *iter = (tree_iterator *)i;
649
751
  int error = 0;
650
- tree_iterator *ti = (tree_iterator *)self;
651
-
652
- if (tree_iterator__at_tree(ti))
653
- error = tree_iterator__push_frame(ti);
654
752
 
655
- return error;
656
- }
657
-
658
- static int tree_iterator__advance_internal(git_iterator *self)
659
- {
660
- int error;
661
- tree_iterator *ti = (tree_iterator *)self;
662
- tree_iterator_frame *tf = ti->head;
753
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
663
754
 
664
- if (tf->current >= tf->n_entries)
665
- return GIT_ITEROVER;
755
+ /* examine tree entries until we find the next one to return */
756
+ while (true) {
757
+ tree_iterator_entry *prev_entry, *entry;
758
+ tree_iterator_frame *frame;
759
+ bool is_tree;
666
760
 
667
- if (!iterator__has_been_accessed(ti))
668
- return 0;
761
+ if ((frame = tree_iterator_current_frame(iter)) == NULL) {
762
+ error = GIT_ITEROVER;
763
+ break;
764
+ }
669
765
 
670
- if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&
671
- tree_iterator__at_tree(ti))
672
- return tree_iterator__advance_into_internal(self);
766
+ /* no more entries in this frame. pop the frame out */
767
+ if (frame->next_idx == frame->entries.length) {
768
+ tree_iterator_frame_pop(iter);
769
+ continue;
770
+ }
673
771
 
674
- if (ti->path_has_filename) {
675
- git_buf_rtruncate_at_char(&ti->path, '/');
676
- ti->path_has_filename = ti->entry_is_current = false;
677
- }
772
+ /* we may have coalesced the contents of case-insensitively same-named
773
+ * directories, so do the sort now.
774
+ */
775
+ if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries))
776
+ git_vector_sort(&frame->entries);
678
777
 
679
- /* scan forward and up, advancing in frame or popping frame when done */
680
- while (!tree_iterator__move_to_next(ti, tf) &&
681
- tree_iterator__pop_frame(ti, false))
682
- tf = ti->head;
778
+ /* we have more entries in the current frame, that's our next entry */
779
+ prev_entry = tree_iterator_current_entry(frame);
780
+ entry = frame->entries.contents[frame->next_idx];
781
+ frame->next_idx++;
683
782
 
684
- /* find next and load trees */
685
- if ((error = tree_iterator__set_next(ti, tf)) < 0)
686
- return error;
783
+ /* we can have collisions when iterating case insensitively. (eg,
784
+ * 'A/a' and 'a/A'). squash this one if it's already been seen.
785
+ */
786
+ if (iterator__ignore_case(&iter->base) &&
787
+ prev_entry &&
788
+ tree_iterator_entry_cmp_icase(prev_entry, entry) == 0)
789
+ continue;
687
790
 
688
- /* deal with include_trees / auto_expand as needed */
689
- if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
690
- return tree_iterator__advance_into_internal(self);
791
+ if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0)
792
+ break;
691
793
 
692
- return 0;
693
- }
794
+ /* if this path is before our start, advance over this entry */
795
+ if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false))
796
+ continue;
694
797
 
695
- static int tree_iterator__current(
696
- const git_index_entry **out, git_iterator *self)
697
- {
698
- const git_index_entry *entry = NULL;
699
- iterator_pathlist__match_t m;
700
- int error;
798
+ /* if this path is after our end, stop */
799
+ if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) {
800
+ error = GIT_ITEROVER;
801
+ break;
802
+ }
701
803
 
702
- do {
703
- if ((error = tree_iterator__current_internal(&entry, self)) < 0)
704
- return error;
804
+ /* if we have a list of paths we're interested in, examine it */
805
+ if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr))
806
+ continue;
705
807
 
706
- if (self->pathlist.length) {
707
- m = iterator_pathlist__match(
708
- self, entry->path, strlen(entry->path));
808
+ is_tree = git_tree_entry__is_tree(entry->tree_entry);
709
809
 
710
- if (m != ITERATOR_PATHLIST_MATCH) {
711
- if ((error = tree_iterator__advance_internal(self)) < 0)
712
- return error;
810
+ /* if we are *not* including trees then advance over this entry */
811
+ if (is_tree && !iterator__include_trees(iter)) {
713
812
 
714
- entry = NULL;
813
+ /* if we've found a tree (and are not returning it to the caller)
814
+ * and we are autoexpanding, then we want to return the first
815
+ * child. push the new directory and advance.
816
+ */
817
+ if (iterator__do_autoexpand(iter)) {
818
+ if ((error = tree_iterator_frame_push(iter, entry)) < 0)
819
+ break;
715
820
  }
821
+
822
+ continue;
716
823
  }
717
- } while (!entry);
824
+
825
+ tree_iterator_set_current(iter, frame, entry);
826
+
827
+ /* if we are autoexpanding, then push this as a new frame, so that
828
+ * the next call to `advance` will dive into this directory.
829
+ */
830
+ if (is_tree && iterator__do_autoexpand(iter))
831
+ error = tree_iterator_frame_push(iter, entry);
832
+
833
+ break;
834
+ }
718
835
 
719
836
  if (out)
720
- *out = entry;
837
+ *out = (error == 0) ? &iter->entry : NULL;
721
838
 
722
839
  return error;
723
840
  }
724
841
 
725
- static int tree_iterator__advance(
726
- const git_index_entry **entry, git_iterator *self)
842
+ static int tree_iterator_advance_into(
843
+ const git_index_entry **out, git_iterator *i)
727
844
  {
728
- int error = tree_iterator__advance_internal(self);
845
+ tree_iterator *iter = (tree_iterator *)i;
846
+ tree_iterator_frame *frame;
847
+ tree_iterator_entry *prev_entry;
848
+ int error;
729
849
 
730
- iterator__clear_entry(entry);
850
+ if (out)
851
+ *out = NULL;
731
852
 
732
- if (error < 0)
733
- return error;
853
+ if ((frame = tree_iterator_current_frame(iter)) == NULL)
854
+ return GIT_ITEROVER;
734
855
 
735
- return tree_iterator__current(entry, self);
736
- }
856
+ /* get the last seen entry */
857
+ prev_entry = tree_iterator_current_entry(frame);
737
858
 
738
- static int tree_iterator__advance_into(
739
- const git_index_entry **entry, git_iterator *self)
740
- {
741
- int error = tree_iterator__advance_into_internal(self);
859
+ /* it's legal to call advance_into when auto-expand is on. in this case,
860
+ * we will have pushed a new (empty) frame on to the stack for this
861
+ * new directory. since it's empty, its current_entry should be null.
862
+ */
863
+ assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL));
742
864
 
743
- iterator__clear_entry(entry);
865
+ if (prev_entry) {
866
+ if (!git_tree_entry__is_tree(prev_entry->tree_entry))
867
+ return 0;
744
868
 
745
- if (error < 0)
746
- return error;
869
+ if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0)
870
+ return error;
871
+ }
747
872
 
748
- return tree_iterator__current(entry, self);
873
+ /* we've advanced into the directory in question, let advance
874
+ * find the first entry
875
+ */
876
+ return tree_iterator_advance(out, i);
749
877
  }
750
878
 
751
- static int tree_iterator__seek(git_iterator *self, const char *prefix)
879
+ static int tree_iterator_advance_over(
880
+ const git_index_entry **out,
881
+ git_iterator_status_t *status,
882
+ git_iterator *i)
752
883
  {
753
- GIT_UNUSED(self); GIT_UNUSED(prefix);
754
- return -1;
884
+ *status = GIT_ITERATOR_STATUS_NORMAL;
885
+ return git_iterator_advance(out, i);
755
886
  }
756
887
 
757
- static int tree_iterator__reset(
758
- git_iterator *self, const char *start, const char *end)
888
+ static void tree_iterator_clear(tree_iterator *iter)
759
889
  {
760
- tree_iterator *ti = (tree_iterator *)self;
890
+ while (iter->frames.size)
891
+ tree_iterator_frame_pop(iter);
761
892
 
762
- tree_iterator__pop_all(ti, false, false);
893
+ git_array_clear(iter->frames);
763
894
 
764
- if (iterator__reset_range(self, start, end) < 0)
765
- return -1;
895
+ git_pool_clear(&iter->entry_pool);
896
+ git_buf_clear(&iter->entry_path);
766
897
 
767
- return tree_iterator__push_frame(ti); /* re-expand root tree */
898
+ iterator_clear(&iter->base);
768
899
  }
769
900
 
770
- static int tree_iterator__at_end(git_iterator *self)
901
+ static int tree_iterator_init(tree_iterator *iter)
771
902
  {
772
- tree_iterator *ti = (tree_iterator *)self;
773
- return (ti->head->current >= ti->head->n_entries);
774
- }
903
+ int error;
775
904
 
776
- static void tree_iterator__free(git_iterator *self)
777
- {
778
- tree_iterator *ti = (tree_iterator *)self;
905
+ git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry));
779
906
 
780
- if (ti->head) {
781
- tree_iterator__pop_all(ti, true, false);
782
- git_tree_free(ti->head->entries[0]->tree);
783
- git__free(ti->head);
784
- }
907
+ if ((error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0)
908
+ return error;
785
909
 
786
- git_pool_clear(&ti->pool);
787
- git_buf_free(&ti->path);
910
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
911
+
912
+ return 0;
788
913
  }
789
914
 
790
- static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree)
915
+ static int tree_iterator_reset(git_iterator *i)
791
916
  {
792
- size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry);
793
- tree_iterator_frame *root = git__calloc(sz, sizeof(char));
794
- GITERR_CHECK_ALLOC(root);
917
+ tree_iterator *iter = (tree_iterator *)i;
795
918
 
796
- root->n_entries = 1;
797
- root->next = 1;
798
- root->start = ti->base.start;
799
- root->startlen = root->start ? strlen(root->start) : 0;
800
- root->entries[0] = git_pool_mallocz(&ti->pool, 1);
801
- GITERR_CHECK_ALLOC(root->entries[0]);
802
- root->entries[0]->tree = tree;
919
+ tree_iterator_clear(iter);
920
+ return tree_iterator_init(iter);
921
+ }
922
+
923
+ static void tree_iterator_free(git_iterator *i)
924
+ {
925
+ tree_iterator *iter = (tree_iterator *)i;
803
926
 
804
- ti->head = ti->root = root;
927
+ tree_iterator_clear(iter);
805
928
 
806
- return 0;
929
+ git_tree_free(iter->root);
930
+ git_buf_free(&iter->entry_path);
807
931
  }
808
932
 
809
933
  int git_iterator_for_tree(
810
- git_iterator **iter,
934
+ git_iterator **out,
811
935
  git_tree *tree,
812
936
  git_iterator_options *options)
813
937
  {
938
+ tree_iterator *iter;
814
939
  int error;
815
- tree_iterator *ti;
816
-
817
- if (tree == NULL)
818
- return git_iterator_for_nothing(iter, options);
819
940
 
820
- if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)
821
- return error;
941
+ static git_iterator_callbacks callbacks = {
942
+ tree_iterator_current,
943
+ tree_iterator_advance,
944
+ tree_iterator_advance_into,
945
+ tree_iterator_advance_over,
946
+ tree_iterator_reset,
947
+ tree_iterator_free
948
+ };
822
949
 
823
- ti = git__calloc(1, sizeof(tree_iterator));
824
- GITERR_CHECK_ALLOC(ti);
950
+ *out = NULL;
825
951
 
826
- ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));
952
+ if (tree == NULL)
953
+ return git_iterator_for_nothing(out, options);
827
954
 
828
- if ((error = iterator__update_ignore_case((git_iterator *)ti, options ? options->flags : 0)) < 0)
829
- goto fail;
955
+ iter = git__calloc(1, sizeof(tree_iterator));
956
+ GITERR_CHECK_ALLOC(iter);
830
957
 
831
- git_pool_init(&ti->pool, sizeof(tree_iterator_entry));
958
+ iter->base.type = GIT_ITERATOR_TYPE_TREE;
959
+ iter->base.cb = &callbacks;
832
960
 
833
- if ((error = tree_iterator__create_root_frame(ti, tree)) < 0 ||
834
- (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */
835
- goto fail;
961
+ if ((error = iterator_init_common(&iter->base,
962
+ git_tree_owner(tree), NULL, options)) < 0 ||
963
+ (error = git_tree_dup(&iter->root, tree)) < 0 ||
964
+ (error = tree_iterator_init(iter)) < 0)
965
+ goto on_error;
836
966
 
837
- *iter = (git_iterator *)ti;
967
+ *out = &iter->base;
838
968
  return 0;
839
969
 
840
- fail:
841
- git_iterator_free((git_iterator *)ti);
970
+ on_error:
971
+ git_iterator_free(&iter->base);
842
972
  return error;
843
973
  }
844
974
 
845
-
846
- typedef struct {
847
- git_iterator base;
848
- git_iterator_callbacks cb;
849
- git_index *index;
850
- git_vector entries;
851
- git_vector_cmp entry_srch;
852
- size_t current;
853
- /* when limiting with a pathlist, this is the current index into it */
854
- size_t pathlist_idx;
855
- /* when not in autoexpand mode, use these to represent "tree" state */
856
- git_buf partial;
857
- size_t partial_pos;
858
- char restore_terminator;
859
- git_index_entry tree_entry;
860
- } index_iterator;
861
-
862
- static const git_index_entry *index_iterator__index_entry(index_iterator *ii)
863
- {
864
- const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
865
-
866
- if (ie != NULL && iterator__past_end(ii, ie->path)) {
867
- ii->current = git_vector_length(&ii->entries);
868
- ie = NULL;
869
- }
870
-
871
- return ie;
872
- }
873
-
874
- static const git_index_entry *index_iterator__advance_over_unwanted(
875
- index_iterator *ii)
975
+ int git_iterator_current_tree_entry(
976
+ const git_tree_entry **tree_entry, git_iterator *i)
876
977
  {
877
- const git_index_entry *ie = index_iterator__index_entry(ii);
878
- bool match;
879
-
880
- while (ie) {
881
- if (!iterator__include_conflicts(ii) &&
882
- git_index_entry_is_conflict(ie)) {
883
- ii->current++;
884
- ie = index_iterator__index_entry(ii);
885
- continue;
886
- }
978
+ tree_iterator *iter;
979
+ tree_iterator_frame *frame;
980
+ tree_iterator_entry *entry;
887
981
 
888
- /* if we have a pathlist, this entry's path must be in it to be
889
- * returned. walk the pathlist in unison with the index to
890
- * compare paths.
891
- */
892
- if (ii->base.pathlist.length) {
893
- match = iterator_pathlist_walk__contains(&ii->base, ie->path);
982
+ assert(i->type == GIT_ITERATOR_TYPE_TREE);
894
983
 
895
- if (!match) {
896
- ii->current++;
897
- ie = index_iterator__index_entry(ii);
898
- continue;
899
- }
900
- }
984
+ iter = (tree_iterator *)i;
901
985
 
902
- break;
903
- }
986
+ frame = tree_iterator_current_frame(iter);
987
+ entry = tree_iterator_current_entry(frame);
904
988
 
905
- return ie;
989
+ *tree_entry = entry->tree_entry;
990
+ return 0;
906
991
  }
907
992
 
908
- static void index_iterator__next_prefix_tree(index_iterator *ii)
993
+ int git_iterator_current_parent_tree(
994
+ const git_tree **parent_tree, git_iterator *i, size_t depth)
909
995
  {
910
- const char *slash;
996
+ tree_iterator *iter;
997
+ tree_iterator_frame *frame;
911
998
 
912
- if (!iterator__include_trees(ii))
913
- return;
999
+ assert(i->type == GIT_ITERATOR_TYPE_TREE);
914
1000
 
915
- slash = strchr(&ii->partial.ptr[ii->partial_pos], '/');
1001
+ iter = (tree_iterator *)i;
916
1002
 
917
- if (slash != NULL) {
918
- ii->partial_pos = (slash - ii->partial.ptr) + 1;
919
- ii->restore_terminator = ii->partial.ptr[ii->partial_pos];
920
- ii->partial.ptr[ii->partial_pos] = '\0';
921
- } else {
922
- ii->partial_pos = ii->partial.size;
923
- }
1003
+ assert(depth < iter->frames.size);
1004
+ frame = &iter->frames.ptr[iter->frames.size-depth-1];
924
1005
 
925
- if (index_iterator__index_entry(ii) == NULL)
926
- ii->partial_pos = ii->partial.size;
1006
+ *parent_tree = frame->tree;
1007
+ return 0;
927
1008
  }
928
1009
 
929
- static int index_iterator__first_prefix_tree(index_iterator *ii)
930
- {
931
- const git_index_entry *ie = index_iterator__advance_over_unwanted(ii);
932
- const char *scan, *prior, *slash;
1010
+ /* Filesystem iterator */
933
1011
 
934
- if (!ie || !iterator__include_trees(ii))
935
- return 0;
1012
+ typedef struct {
1013
+ struct stat st;
1014
+ size_t path_len;
1015
+ iterator_pathlist_search_t match;
1016
+ char path[GIT_FLEX_ARRAY];
1017
+ } filesystem_iterator_entry;
936
1018
 
937
- /* find longest common prefix with prior index entry */
938
- for (scan = slash = ie->path, prior = ii->partial.ptr;
939
- *scan && *scan == *prior; ++scan, ++prior)
940
- if (*scan == '/')
941
- slash = scan;
1019
+ typedef struct {
1020
+ git_vector entries;
1021
+ git_pool entry_pool;
1022
+ size_t next_idx;
942
1023
 
943
- if (git_buf_sets(&ii->partial, ie->path) < 0)
944
- return -1;
1024
+ size_t path_len;
1025
+ int is_ignored;
1026
+ } filesystem_iterator_frame;
945
1027
 
946
- ii->partial_pos = (slash - ie->path) + 1;
947
- index_iterator__next_prefix_tree(ii);
1028
+ typedef struct {
1029
+ git_iterator base;
1030
+ char *root;
1031
+ size_t root_len;
948
1032
 
949
- return 0;
950
- }
1033
+ unsigned int dirload_flags;
951
1034
 
952
- #define index_iterator__at_tree(I) \
953
- (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size)
1035
+ git_tree *tree;
1036
+ git_index *index;
1037
+ git_vector index_snapshot;
954
1038
 
955
- static int index_iterator__current(
956
- const git_index_entry **entry, git_iterator *self)
957
- {
958
- index_iterator *ii = (index_iterator *)self;
959
- const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
1039
+ git_array_t(filesystem_iterator_frame) frames;
1040
+ git_ignores ignores;
960
1041
 
961
- if (ie != NULL && index_iterator__at_tree(ii)) {
962
- ii->tree_entry.path = ii->partial.ptr;
963
- ie = &ii->tree_entry;
964
- }
1042
+ /* info about the current entry */
1043
+ git_index_entry entry;
1044
+ git_buf current_path;
1045
+ int current_is_ignored;
965
1046
 
966
- if (entry)
967
- *entry = ie;
1047
+ /* temporary buffer for advance_over */
1048
+ git_buf tmp_buf;
1049
+ } filesystem_iterator;
968
1050
 
969
- ii->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
970
1051
 
971
- return (ie != NULL) ? 0 : GIT_ITEROVER;
1052
+ GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame(
1053
+ filesystem_iterator *iter)
1054
+ {
1055
+ return iter->frames.size > 1 ?
1056
+ &iter->frames.ptr[iter->frames.size-2] : NULL;
972
1057
  }
973
1058
 
974
- static int index_iterator__at_end(git_iterator *self)
1059
+ GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame(
1060
+ filesystem_iterator *iter)
975
1061
  {
976
- index_iterator *ii = (index_iterator *)self;
977
- return (ii->current >= git_vector_length(&ii->entries));
1062
+ return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL;
978
1063
  }
979
1064
 
980
- static int index_iterator__advance(
981
- const git_index_entry **entry, git_iterator *self)
1065
+ GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry(
1066
+ filesystem_iterator_frame *frame)
982
1067
  {
983
- index_iterator *ii = (index_iterator *)self;
984
- size_t entrycount = git_vector_length(&ii->entries);
985
- const git_index_entry *ie;
986
-
987
- if (!iterator__has_been_accessed(ii))
988
- return index_iterator__current(entry, self);
989
-
990
- if (index_iterator__at_tree(ii)) {
991
- if (iterator__do_autoexpand(ii)) {
992
- ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
993
- index_iterator__next_prefix_tree(ii);
994
- } else {
995
- /* advance to sibling tree (i.e. find entry with new prefix) */
996
- while (ii->current < entrycount) {
997
- ii->current++;
998
-
999
- if (!(ie = git_vector_get(&ii->entries, ii->current)) ||
1000
- ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0)
1001
- break;
1002
- }
1003
-
1004
- if (index_iterator__first_prefix_tree(ii) < 0)
1005
- return -1;
1006
- }
1007
- } else {
1008
- if (ii->current < entrycount)
1009
- ii->current++;
1010
-
1011
- if (index_iterator__first_prefix_tree(ii) < 0)
1012
- return -1;
1013
- }
1014
-
1015
- return index_iterator__current(entry, self);
1068
+ return frame->next_idx == 0 ?
1069
+ NULL : frame->entries.contents[frame->next_idx-1];
1016
1070
  }
1017
1071
 
1018
- static int index_iterator__advance_into(
1019
- const git_index_entry **entry, git_iterator *self)
1072
+ static int filesystem_iterator_entry_cmp(const void *_a, const void *_b)
1020
1073
  {
1021
- index_iterator *ii = (index_iterator *)self;
1022
- const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
1023
-
1024
- if (ie != NULL && index_iterator__at_tree(ii)) {
1025
- if (ii->restore_terminator)
1026
- ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
1027
- index_iterator__next_prefix_tree(ii);
1028
- }
1074
+ const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
1075
+ const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;
1029
1076
 
1030
- return index_iterator__current(entry, self);
1077
+ return git__strcmp(a->path, b->path);
1031
1078
  }
1032
1079
 
1033
- static int index_iterator__seek(git_iterator *self, const char *prefix)
1080
+ static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b)
1034
1081
  {
1035
- GIT_UNUSED(self); GIT_UNUSED(prefix);
1036
- return -1;
1082
+ const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
1083
+ const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;
1084
+
1085
+ return git__strcasecmp(a->path, b->path);
1037
1086
  }
1038
1087
 
1039
- static int index_iterator__reset(
1040
- git_iterator *self, const char *start, const char *end)
1088
+ #define FILESYSTEM_MAX_DEPTH 100
1089
+
1090
+ /**
1091
+ * Figure out if an entry is a submodule.
1092
+ *
1093
+ * We consider it a submodule if the path is listed as a submodule in
1094
+ * either the tree or the index.
1095
+ */
1096
+ static int filesystem_iterator_is_submodule(
1097
+ bool *out, filesystem_iterator *iter, const char *path, size_t path_len)
1041
1098
  {
1042
- index_iterator *ii = (index_iterator *)self;
1043
- const git_index_entry *ie;
1099
+ bool is_submodule = false;
1100
+ int error;
1044
1101
 
1045
- if (iterator__reset_range(self, start, end) < 0)
1046
- return -1;
1102
+ *out = false;
1047
1103
 
1048
- ii->current = 0;
1104
+ /* first see if this path is a submodule in HEAD */
1105
+ if (iter->tree) {
1106
+ git_tree_entry *entry;
1049
1107
 
1050
- iterator_pathlist_walk__reset(self);
1108
+ error = git_tree_entry_bypath(&entry, iter->tree, path);
1051
1109
 
1052
- /* if we're given a start prefix, find it; if we're given a pathlist, find
1053
- * the first of those. start at the later of the two.
1054
- */
1055
- if (ii->base.start)
1056
- git_index_snapshot_find(
1057
- &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0);
1110
+ if (error < 0 && error != GIT_ENOTFOUND)
1111
+ return error;
1058
1112
 
1059
- if ((ie = index_iterator__advance_over_unwanted(ii)) == NULL)
1060
- return 0;
1113
+ if (!error) {
1114
+ is_submodule = (entry->attr == GIT_FILEMODE_COMMIT);
1115
+ git_tree_entry_free(entry);
1116
+ }
1117
+ }
1061
1118
 
1062
- if (git_buf_sets(&ii->partial, ie->path) < 0)
1063
- return -1;
1119
+ if (!is_submodule && iter->base.index) {
1120
+ size_t pos;
1064
1121
 
1065
- ii->partial_pos = 0;
1122
+ error = git_index_snapshot_find(&pos,
1123
+ &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0);
1066
1124
 
1067
- if (ii->base.start) {
1068
- size_t startlen = strlen(ii->base.start);
1125
+ if (error < 0 && error != GIT_ENOTFOUND)
1126
+ return error;
1069
1127
 
1070
- ii->partial_pos = (startlen > ii->partial.size) ?
1071
- ii->partial.size : startlen;
1128
+ if (!error) {
1129
+ git_index_entry *e = git_vector_get(&iter->index_snapshot, pos);
1130
+ is_submodule = (e->mode == GIT_FILEMODE_COMMIT);
1131
+ }
1072
1132
  }
1073
1133
 
1074
- index_iterator__next_prefix_tree(ii);
1075
-
1134
+ *out = is_submodule;
1076
1135
  return 0;
1077
1136
  }
1078
1137
 
1079
- static void index_iterator__free(git_iterator *self)
1138
+ static void filesystem_iterator_frame_push_ignores(
1139
+ filesystem_iterator *iter,
1140
+ filesystem_iterator_entry *frame_entry,
1141
+ filesystem_iterator_frame *new_frame)
1080
1142
  {
1081
- index_iterator *ii = (index_iterator *)self;
1082
- git_index_snapshot_release(&ii->entries, ii->index);
1083
- ii->index = NULL;
1084
- git_buf_free(&ii->partial);
1085
- }
1143
+ filesystem_iterator_frame *previous_frame;
1144
+ const char *path = frame_entry ? frame_entry->path : "";
1086
1145
 
1087
- int git_iterator_for_index(
1088
- git_iterator **iter,
1089
- git_repository *repo,
1090
- git_index *index,
1091
- git_iterator_options *options)
1092
- {
1093
- int error = 0;
1094
- index_iterator *ii = git__calloc(1, sizeof(index_iterator));
1095
- GITERR_CHECK_ALLOC(ii);
1146
+ if (!iterator__honor_ignores(&iter->base))
1147
+ return;
1096
1148
 
1097
- if ((error = git_index_snapshot_new(&ii->entries, index)) < 0) {
1098
- git__free(ii);
1099
- return error;
1149
+ if (git_ignore__lookup(&new_frame->is_ignored,
1150
+ &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) {
1151
+ giterr_clear();
1152
+ new_frame->is_ignored = GIT_IGNORE_NOTFOUND;
1100
1153
  }
1101
- ii->index = index;
1102
1154
 
1103
- ITERATOR_BASE_INIT(ii, index, INDEX, repo);
1104
-
1105
- if ((error = iterator__update_ignore_case((git_iterator *)ii, options ? options->flags : 0)) < 0) {
1106
- git_iterator_free((git_iterator *)ii);
1107
- return error;
1108
- }
1155
+ /* if this is not the top level directory... */
1156
+ if (frame_entry) {
1157
+ const char *relative_path;
1109
1158
 
1110
- ii->entry_srch = iterator__ignore_case(ii) ?
1111
- git_index_entry_isrch : git_index_entry_srch;
1159
+ previous_frame = filesystem_iterator_parent_frame(iter);
1112
1160
 
1113
- git_vector_set_cmp(&ii->entries, iterator__ignore_case(ii) ?
1114
- git_index_entry_icmp : git_index_entry_cmp);
1115
- git_vector_sort(&ii->entries);
1161
+ /* push new ignores for files in this directory */
1162
+ relative_path = frame_entry->path + previous_frame->path_len;
1116
1163
 
1117
- git_buf_init(&ii->partial, 0);
1118
- ii->tree_entry.mode = GIT_FILEMODE_TREE;
1164
+ /* inherit ignored from parent if no rule specified */
1165
+ if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND)
1166
+ new_frame->is_ignored = previous_frame->is_ignored;
1119
1167
 
1120
- index_iterator__reset((git_iterator *)ii, NULL, NULL);
1168
+ git_ignore__push_dir(&iter->ignores, relative_path);
1169
+ }
1170
+ }
1121
1171
 
1122
- *iter = (git_iterator *)ii;
1123
- return 0;
1172
+ static void filesystem_iterator_frame_pop_ignores(
1173
+ filesystem_iterator *iter)
1174
+ {
1175
+ if (iterator__honor_ignores(&iter->base))
1176
+ git_ignore__pop_dir(&iter->ignores);
1124
1177
  }
1125
1178
 
1179
+ GIT_INLINE(bool) filesystem_iterator_examine_path(
1180
+ bool *is_dir_out,
1181
+ iterator_pathlist_search_t *match_out,
1182
+ filesystem_iterator *iter,
1183
+ filesystem_iterator_entry *frame_entry,
1184
+ const char *path,
1185
+ size_t path_len)
1186
+ {
1187
+ bool is_dir = 0;
1188
+ iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL;
1126
1189
 
1127
- typedef struct fs_iterator_frame fs_iterator_frame;
1128
- struct fs_iterator_frame {
1129
- fs_iterator_frame *next;
1130
- git_vector entries;
1131
- size_t index;
1132
- int is_ignored;
1133
- };
1190
+ *is_dir_out = false;
1191
+ *match_out = ITERATOR_PATHLIST_NONE;
1134
1192
 
1135
- typedef struct fs_iterator fs_iterator;
1136
- struct fs_iterator {
1137
- git_iterator base;
1138
- git_iterator_callbacks cb;
1139
- fs_iterator_frame *stack;
1140
- git_index_entry entry;
1141
- git_buf path;
1142
- size_t root_len;
1143
- uint32_t dirload_flags;
1144
- int depth;
1145
- iterator_pathlist__match_t pathlist_match;
1193
+ if (iter->base.start_len) {
1194
+ int cmp = iter->base.strncomp(path, iter->base.start, path_len);
1146
1195
 
1147
- int (*enter_dir_cb)(fs_iterator *self);
1148
- int (*leave_dir_cb)(fs_iterator *self);
1149
- int (*update_entry_cb)(fs_iterator *self);
1150
- };
1196
+ /* we haven't stat'ed `path` yet, so we don't yet know if it's a
1197
+ * directory or not. special case if the current path may be a
1198
+ * directory that matches the start prefix.
1199
+ */
1200
+ if (cmp == 0) {
1201
+ if (iter->base.start[path_len] == '/')
1202
+ is_dir = true;
1151
1203
 
1152
- #define FS_MAX_DEPTH 100
1204
+ else if (iter->base.start[path_len] != '\0')
1205
+ cmp = -1;
1206
+ }
1153
1207
 
1154
- typedef struct {
1155
- struct stat st;
1156
- iterator_pathlist__match_t pathlist_match;
1157
- size_t path_len;
1158
- char path[GIT_FLEX_ARRAY];
1159
- } fs_iterator_path_with_stat;
1208
+ if (cmp < 0)
1209
+ return false;
1210
+ }
1160
1211
 
1161
- static int fs_iterator_path_with_stat_cmp(const void *a, const void *b)
1162
- {
1163
- const fs_iterator_path_with_stat *psa = a, *psb = b;
1164
- return strcmp(psa->path, psb->path);
1165
- }
1212
+ if (iter->base.end_len) {
1213
+ int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len);
1166
1214
 
1167
- static int fs_iterator_path_with_stat_cmp_icase(const void *a, const void *b)
1168
- {
1169
- const fs_iterator_path_with_stat *psa = a, *psb = b;
1170
- return strcasecmp(psa->path, psb->path);
1171
- }
1215
+ if (cmp > 0)
1216
+ return false;
1217
+ }
1172
1218
 
1173
- static fs_iterator_frame *fs_iterator__alloc_frame(fs_iterator *fi)
1174
- {
1175
- fs_iterator_frame *ff = git__calloc(1, sizeof(fs_iterator_frame));
1176
- git_vector_cmp entry_compare = CASESELECT(
1177
- iterator__ignore_case(fi),
1178
- fs_iterator_path_with_stat_cmp_icase,
1179
- fs_iterator_path_with_stat_cmp);
1219
+ /* if we have a pathlist that we're limiting to, examine this path now
1220
+ * to avoid a `stat` if we're not interested in the path.
1221
+ */
1222
+ if (iter->base.pathlist.length) {
1223
+ /* if our parent was explicitly included, so too are we */
1224
+ if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT)
1225
+ match = ITERATOR_PATHLIST_FULL;
1226
+ else
1227
+ match = iterator_pathlist_search(&iter->base, path, path_len);
1180
1228
 
1181
- if (ff && git_vector_init(&ff->entries, 0, entry_compare) < 0) {
1182
- git__free(ff);
1183
- ff = NULL;
1184
- }
1229
+ if (match == ITERATOR_PATHLIST_NONE)
1230
+ return false;
1185
1231
 
1186
- return ff;
1187
- }
1232
+ /* Ensure that the pathlist entry lines up with what we expected */
1233
+ if (match == ITERATOR_PATHLIST_IS_DIR ||
1234
+ match == ITERATOR_PATHLIST_IS_PARENT)
1235
+ is_dir = true;
1236
+ }
1188
1237
 
1189
- static void fs_iterator__free_frame(fs_iterator_frame *ff)
1190
- {
1191
- git_vector_free_deep(&ff->entries);
1192
- git__free(ff);
1238
+ *is_dir_out = is_dir;
1239
+ *match_out = match;
1240
+ return true;
1193
1241
  }
1194
1242
 
1195
- static void fs_iterator__pop_frame(
1196
- fs_iterator *fi, fs_iterator_frame *ff, bool pop_last)
1243
+ GIT_INLINE(bool) filesystem_iterator_is_dot_git(
1244
+ filesystem_iterator *iter, const char *path, size_t path_len)
1197
1245
  {
1198
- if (fi && fi->stack == ff) {
1199
- if (!ff->next && !pop_last) {
1200
- memset(&fi->entry, 0, sizeof(fi->entry));
1201
- return;
1202
- }
1246
+ size_t len;
1203
1247
 
1204
- if (fi->leave_dir_cb)
1205
- (void)fi->leave_dir_cb(fi);
1248
+ if (!iterator__ignore_dot_git(&iter->base))
1249
+ return false;
1206
1250
 
1207
- fi->stack = ff->next;
1208
- fi->depth--;
1209
- }
1251
+ if ((len = path_len) < 4)
1252
+ return false;
1210
1253
 
1211
- fs_iterator__free_frame(ff);
1212
- }
1254
+ if (path[len - 1] == '/')
1255
+ len--;
1213
1256
 
1214
- static int fs_iterator__update_entry(fs_iterator *fi);
1215
- static int fs_iterator__advance_over(
1216
- const git_index_entry **entry, git_iterator *self);
1257
+ if (git__tolower(path[len - 1]) != 't' ||
1258
+ git__tolower(path[len - 2]) != 'i' ||
1259
+ git__tolower(path[len - 3]) != 'g' ||
1260
+ git__tolower(path[len - 4]) != '.')
1261
+ return false;
1217
1262
 
1218
- static int fs_iterator__entry_cmp(const void *i, const void *item)
1219
- {
1220
- const fs_iterator *fi = (const fs_iterator *)i;
1221
- const fs_iterator_path_with_stat *ps = item;
1222
- return fi->base.prefixcomp(fi->base.start, ps->path);
1263
+ return (len == 4 || path[len - 5] == '/');
1223
1264
  }
1224
1265
 
1225
- static void fs_iterator__seek_frame_start(
1226
- fs_iterator *fi, fs_iterator_frame *ff)
1266
+ static filesystem_iterator_entry *filesystem_iterator_entry_init(
1267
+ filesystem_iterator_frame *frame,
1268
+ const char *path,
1269
+ size_t path_len,
1270
+ struct stat *statbuf,
1271
+ iterator_pathlist_search_t pathlist_match)
1227
1272
  {
1228
- if (!ff)
1229
- return;
1273
+ filesystem_iterator_entry *entry;
1274
+ size_t entry_size;
1230
1275
 
1231
- if (fi->base.start)
1232
- git_vector_bsearch2(
1233
- &ff->index, &ff->entries, fs_iterator__entry_cmp, fi);
1234
- else
1235
- ff->index = 0;
1276
+ /* Make sure to append two bytes, one for the path's null
1277
+ * termination, one for a possible trailing '/' for folders.
1278
+ */
1279
+ if (GIT_ADD_SIZET_OVERFLOW(&entry_size,
1280
+ sizeof(filesystem_iterator_entry), path_len) ||
1281
+ GIT_ADD_SIZET_OVERFLOW(&entry_size, entry_size, 2) ||
1282
+ (entry = git_pool_malloc(&frame->entry_pool, entry_size)) == NULL)
1283
+ return NULL;
1284
+
1285
+ entry->path_len = path_len;
1286
+ entry->match = pathlist_match;
1287
+ memcpy(entry->path, path, path_len);
1288
+ memcpy(&entry->st, statbuf, sizeof(struct stat));
1289
+
1290
+ /* Suffix directory paths with a '/' */
1291
+ if (S_ISDIR(entry->st.st_mode))
1292
+ entry->path[entry->path_len++] = '/';
1293
+
1294
+ entry->path[entry->path_len] = '\0';
1295
+
1296
+ return entry;
1236
1297
  }
1237
1298
 
1238
- static int dirload_with_stat(git_vector *contents, fs_iterator *fi)
1299
+ static int filesystem_iterator_frame_push(
1300
+ filesystem_iterator *iter,
1301
+ filesystem_iterator_entry *frame_entry)
1239
1302
  {
1303
+ filesystem_iterator_frame *new_frame = NULL;
1240
1304
  git_path_diriter diriter = GIT_PATH_DIRITER_INIT;
1305
+ git_buf root = GIT_BUF_INIT;
1241
1306
  const char *path;
1242
- size_t start_len = fi->base.start ? strlen(fi->base.start) : 0;
1243
- size_t end_len = fi->base.end ? strlen(fi->base.end) : 0;
1244
- fs_iterator_path_with_stat *ps;
1245
- size_t path_len, cmp_len, ps_size;
1246
- iterator_pathlist__match_t pathlist_match = ITERATOR_PATHLIST_MATCH;
1307
+ filesystem_iterator_entry *entry;
1308
+ struct stat statbuf;
1309
+ size_t path_len;
1247
1310
  int error;
1248
1311
 
1312
+ if (iter->frames.size == FILESYSTEM_MAX_DEPTH) {
1313
+ giterr_set(GITERR_REPOSITORY,
1314
+ "directory nesting too deep (%"PRIuZ")", iter->frames.size);
1315
+ return -1;
1316
+ }
1317
+
1318
+ new_frame = git_array_alloc(iter->frames);
1319
+ GITERR_CHECK_ALLOC(new_frame);
1320
+
1321
+ memset(new_frame, 0, sizeof(filesystem_iterator_frame));
1322
+
1323
+ if (frame_entry)
1324
+ git_buf_joinpath(&root, iter->root, frame_entry->path);
1325
+ else
1326
+ git_buf_puts(&root, iter->root);
1327
+
1328
+ if (git_buf_oom(&root)) {
1329
+ error = -1;
1330
+ goto done;
1331
+ }
1332
+
1333
+ new_frame->path_len = frame_entry ? frame_entry->path_len : 0;
1334
+
1249
1335
  /* Any error here is equivalent to the dir not existing, skip over it */
1250
1336
  if ((error = git_path_diriter_init(
1251
- &diriter, fi->path.ptr, fi->dirload_flags)) < 0) {
1337
+ &diriter, root.ptr, iter->dirload_flags)) < 0) {
1252
1338
  error = GIT_ENOTFOUND;
1253
1339
  goto done;
1254
1340
  }
1255
1341
 
1342
+ if ((error = git_vector_init(&new_frame->entries, 64,
1343
+ iterator__ignore_case(&iter->base) ?
1344
+ filesystem_iterator_entry_cmp_icase :
1345
+ filesystem_iterator_entry_cmp)) < 0)
1346
+ goto done;
1347
+
1348
+ git_pool_init(&new_frame->entry_pool, 1);
1349
+
1350
+ /* check if this directory is ignored */
1351
+ filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame);
1352
+
1256
1353
  while ((error = git_path_diriter_next(&diriter)) == 0) {
1354
+ iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL;
1355
+ bool dir_expected = false;
1356
+
1257
1357
  if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0)
1258
1358
  goto done;
1259
1359
 
1260
- assert(path_len > fi->root_len);
1360
+ assert(path_len > iter->root_len);
1261
1361
 
1262
1362
  /* remove the prefix if requested */
1263
- path += fi->root_len;
1264
- path_len -= fi->root_len;
1265
-
1266
- /* skip if before start_stat or after end_stat */
1267
- cmp_len = min(start_len, path_len);
1268
- if (cmp_len && fi->base.strncomp(path, fi->base.start, cmp_len) < 0)
1269
- continue;
1270
- /* skip if after end_stat */
1271
- cmp_len = min(end_len, path_len);
1272
- if (cmp_len && fi->base.strncomp(path, fi->base.end, cmp_len) > 0)
1273
- continue;
1363
+ path += iter->root_len;
1364
+ path_len -= iter->root_len;
1274
1365
 
1275
- /* if we have a pathlist that we're limiting to, examine this path.
1276
- * if the frame has already deemed us inside the path (eg, we're in
1277
- * `foo/bar` and the pathlist previously was detected to say `foo/`)
1278
- * then simply continue. otherwise, examine the pathlist looking for
1279
- * this path or children of this path.
1366
+ /* examine start / end and the pathlist to see if this path is in it.
1367
+ * note that since we haven't yet stat'ed the path, we cannot know
1368
+ * whether it's a directory yet or not, so this can give us an
1369
+ * expected type (S_IFDIR or S_IFREG) that we should examine)
1280
1370
  */
1281
- if (fi->base.pathlist.length &&
1282
- fi->pathlist_match != ITERATOR_PATHLIST_MATCH &&
1283
- fi->pathlist_match != ITERATOR_PATHLIST_MATCH_DIRECTORY &&
1284
- !(pathlist_match = iterator_pathlist__match(&fi->base, path, path_len)))
1371
+ if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match,
1372
+ iter, frame_entry, path, path_len))
1285
1373
  continue;
1286
1374
 
1287
- /* Make sure to append two bytes, one for the path's null
1288
- * termination, one for a possible trailing '/' for folders.
1375
+ /* TODO: don't need to stat if assume unchanged for this path and
1376
+ * we have an index, we can just copy the data out of it.
1289
1377
  */
1290
- GITERR_CHECK_ALLOC_ADD(&ps_size, sizeof(fs_iterator_path_with_stat), path_len);
1291
- GITERR_CHECK_ALLOC_ADD(&ps_size, ps_size, 2);
1292
1378
 
1293
- ps = git__calloc(1, ps_size);
1294
- ps->path_len = path_len;
1379
+ if ((error = git_path_diriter_stat(&statbuf, &diriter)) < 0) {
1380
+ /* file was removed between readdir and lstat */
1381
+ if (error == GIT_ENOTFOUND)
1382
+ continue;
1295
1383
 
1296
- memcpy(ps->path, path, path_len);
1384
+ /* treat the file as unreadable */
1385
+ memset(&statbuf, 0, sizeof(statbuf));
1386
+ statbuf.st_mode = GIT_FILEMODE_UNREADABLE;
1297
1387
 
1298
- /* TODO: don't stat if assume unchanged for this path */
1388
+ error = 0;
1389
+ }
1299
1390
 
1300
- if ((error = git_path_diriter_stat(&ps->st, &diriter)) < 0) {
1301
- if (error == GIT_ENOTFOUND) {
1302
- /* file was removed between readdir and lstat */
1303
- git__free(ps);
1304
- continue;
1305
- }
1391
+ iter->base.stat_calls++;
1306
1392
 
1307
- if (pathlist_match == ITERATOR_PATHLIST_MATCH_DIRECTORY) {
1308
- /* were looking for a directory, but this is a file */
1309
- git__free(ps);
1310
- continue;
1311
- }
1312
-
1313
- /* Treat the file as unreadable if we get any other error */
1314
- memset(&ps->st, 0, sizeof(ps->st));
1315
- ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
1393
+ /* Ignore wacky things in the filesystem */
1394
+ if (!S_ISDIR(statbuf.st_mode) &&
1395
+ !S_ISREG(statbuf.st_mode) &&
1396
+ !S_ISLNK(statbuf.st_mode) &&
1397
+ statbuf.st_mode != GIT_FILEMODE_UNREADABLE)
1398
+ continue;
1316
1399
 
1317
- giterr_clear();
1318
- error = 0;
1319
- } else if (S_ISDIR(ps->st.st_mode)) {
1320
- /* Suffix directory paths with a '/' */
1321
- ps->path[ps->path_len++] = '/';
1322
- ps->path[ps->path_len] = '\0';
1323
- } else if(!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) {
1324
- /* Ignore wacky things in the filesystem */
1325
- git__free(ps);
1400
+ if (filesystem_iterator_is_dot_git(iter, path, path_len))
1326
1401
  continue;
1402
+
1403
+ /* convert submodules to GITLINK and remove trailing slashes */
1404
+ if (S_ISDIR(statbuf.st_mode)) {
1405
+ bool submodule = false;
1406
+
1407
+ if ((error = filesystem_iterator_is_submodule(&submodule,
1408
+ iter, path, path_len)) < 0)
1409
+ goto done;
1410
+
1411
+ if (submodule)
1412
+ statbuf.st_mode = GIT_FILEMODE_COMMIT;
1327
1413
  }
1328
1414
 
1329
- /* record whether this path was explicitly found in the path list
1330
- * or whether we're only examining it because something beneath it
1331
- * is in the path list.
1332
- */
1333
- ps->pathlist_match = pathlist_match;
1334
- git_vector_insert(contents, ps);
1415
+ /* Ensure that the pathlist entry lines up with what we expected */
1416
+ else if (dir_expected)
1417
+ continue;
1418
+
1419
+ entry = filesystem_iterator_entry_init(new_frame,
1420
+ path, path_len, &statbuf, pathlist_match);
1421
+ GITERR_CHECK_ALLOC(entry);
1422
+
1423
+ git_vector_insert(&new_frame->entries, entry);
1335
1424
  }
1336
1425
 
1337
1426
  if (error == GIT_ITEROVER)
1338
1427
  error = 0;
1339
1428
 
1340
1429
  /* sort now that directory suffix is added */
1341
- git_vector_sort(contents);
1430
+ git_vector_sort(&new_frame->entries);
1342
1431
 
1343
1432
  done:
1433
+ if (error < 0)
1434
+ git_array_pop(iter->frames);
1435
+
1436
+ git_buf_free(&root);
1344
1437
  git_path_diriter_free(&diriter);
1345
1438
  return error;
1346
1439
  }
1347
1440
 
1348
-
1349
- static int fs_iterator__expand_dir(fs_iterator *fi)
1441
+ GIT_INLINE(void) filesystem_iterator_frame_pop(filesystem_iterator *iter)
1350
1442
  {
1351
- int error;
1352
- fs_iterator_frame *ff;
1353
-
1354
- if (fi->depth > FS_MAX_DEPTH) {
1355
- giterr_set(GITERR_REPOSITORY,
1356
- "Directory nesting is too deep (%d)", fi->depth);
1357
- return -1;
1358
- }
1359
-
1360
- ff = fs_iterator__alloc_frame(fi);
1361
- GITERR_CHECK_ALLOC(ff);
1443
+ filesystem_iterator_frame *frame;
1362
1444
 
1363
- error = dirload_with_stat(&ff->entries, fi);
1364
-
1365
- if (error < 0) {
1366
- git_error_state last_error = { 0 };
1367
- giterr_state_capture(&last_error, error);
1445
+ assert(iter->frames.size);
1368
1446
 
1369
- /* these callbacks may clear the error message */
1370
- fs_iterator__free_frame(ff);
1371
- fs_iterator__advance_over(NULL, (git_iterator *)fi);
1372
- /* next time return value we skipped to */
1373
- fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
1447
+ frame = git_array_pop(iter->frames);
1448
+ filesystem_iterator_frame_pop_ignores(iter);
1374
1449
 
1375
- return giterr_state_restore(&last_error);
1376
- }
1450
+ git_pool_clear(&frame->entry_pool);
1451
+ git_vector_free(&frame->entries);
1452
+ }
1377
1453
 
1378
- if (ff->entries.length == 0) {
1379
- fs_iterator__free_frame(ff);
1380
- return GIT_ENOTFOUND;
1381
- }
1382
- fi->base.stat_calls += ff->entries.length;
1454
+ static void filesystem_iterator_set_current(
1455
+ filesystem_iterator *iter,
1456
+ filesystem_iterator_entry *entry)
1457
+ {
1458
+ iter->entry.ctime.seconds = entry->st.st_ctime;
1459
+ iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec;
1383
1460
 
1384
- fs_iterator__seek_frame_start(fi, ff);
1461
+ iter->entry.mtime.seconds = entry->st.st_mtime;
1462
+ iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec;
1385
1463
 
1386
- ff->next = fi->stack;
1387
- fi->stack = ff;
1388
- fi->depth++;
1464
+ iter->entry.dev = entry->st.st_dev;
1465
+ iter->entry.ino = entry->st.st_ino;
1466
+ iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode);
1467
+ iter->entry.uid = entry->st.st_uid;
1468
+ iter->entry.gid = entry->st.st_gid;
1469
+ iter->entry.file_size = entry->st.st_size;
1389
1470
 
1390
- if (fi->enter_dir_cb && (error = fi->enter_dir_cb(fi)) < 0)
1391
- return error;
1471
+ iter->entry.path = entry->path;
1392
1472
 
1393
- return fs_iterator__update_entry(fi);
1473
+ iter->current_is_ignored = GIT_IGNORE_UNCHECKED;
1394
1474
  }
1395
1475
 
1396
- static int fs_iterator__current(
1397
- const git_index_entry **entry, git_iterator *self)
1476
+ static int filesystem_iterator_current(
1477
+ const git_index_entry **out, git_iterator *i)
1398
1478
  {
1399
- fs_iterator *fi = (fs_iterator *)self;
1400
- const git_index_entry *fe = (fi->entry.path == NULL) ? NULL : &fi->entry;
1479
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1401
1480
 
1402
- if (entry)
1403
- *entry = fe;
1481
+ if (!iterator__has_been_accessed(i))
1482
+ return iter->base.cb->advance(out, i);
1404
1483
 
1405
- fi->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
1406
-
1407
- return (fe != NULL) ? 0 : GIT_ITEROVER;
1408
- }
1484
+ if (!iter->frames.size) {
1485
+ *out = NULL;
1486
+ return GIT_ITEROVER;
1487
+ }
1409
1488
 
1410
- static int fs_iterator__at_end(git_iterator *self)
1411
- {
1412
- return (((fs_iterator *)self)->entry.path == NULL);
1489
+ *out = &iter->entry;
1490
+ return 0;
1413
1491
  }
1414
1492
 
1415
- static int fs_iterator__advance_into(
1416
- const git_index_entry **entry, git_iterator *iter)
1493
+ static int filesystem_iterator_advance(
1494
+ const git_index_entry **out, git_iterator *i)
1417
1495
  {
1496
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1418
1497
  int error = 0;
1419
- fs_iterator *fi = (fs_iterator *)iter;
1420
1498
 
1421
- iterator__clear_entry(entry);
1499
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
1422
1500
 
1423
- /* Allow you to explicitly advance into a commit/submodule (as well as a
1424
- * tree) to avoid cases where an entry is mislabeled as a submodule in
1425
- * the working directory. The fs iterator will never have COMMMIT
1426
- * entries on it's own, but a wrapper might add them.
1427
- */
1428
- if (fi->entry.path != NULL &&
1429
- (fi->entry.mode == GIT_FILEMODE_TREE ||
1430
- fi->entry.mode == GIT_FILEMODE_COMMIT))
1431
- /* returns GIT_ENOTFOUND if the directory is empty */
1432
- error = fs_iterator__expand_dir(fi);
1501
+ /* examine filesystem entries until we find the next one to return */
1502
+ while (true) {
1503
+ filesystem_iterator_frame *frame;
1504
+ filesystem_iterator_entry *entry;
1433
1505
 
1434
- if (!error && entry)
1435
- error = fs_iterator__current(entry, iter);
1506
+ if ((frame = filesystem_iterator_current_frame(iter)) == NULL) {
1507
+ error = GIT_ITEROVER;
1508
+ break;
1509
+ }
1436
1510
 
1437
- if (!error && !fi->entry.path)
1438
- error = GIT_ITEROVER;
1511
+ /* no more entries in this frame. pop the frame out */
1512
+ if (frame->next_idx == frame->entries.length) {
1513
+ filesystem_iterator_frame_pop(iter);
1514
+ continue;
1515
+ }
1439
1516
 
1440
- return error;
1441
- }
1517
+ /* we have more entries in the current frame, that's our next entry */
1518
+ entry = frame->entries.contents[frame->next_idx];
1519
+ frame->next_idx++;
1442
1520
 
1443
- static void fs_iterator__advance_over_internal(git_iterator *self)
1444
- {
1445
- fs_iterator *fi = (fs_iterator *)self;
1446
- fs_iterator_frame *ff;
1447
- fs_iterator_path_with_stat *next;
1521
+ if (S_ISDIR(entry->st.st_mode)) {
1522
+ if (iterator__do_autoexpand(iter)) {
1523
+ error = filesystem_iterator_frame_push(iter, entry);
1448
1524
 
1449
- while (fi->entry.path != NULL) {
1450
- ff = fi->stack;
1451
- next = git_vector_get(&ff->entries, ++ff->index);
1525
+ /* may get GIT_ENOTFOUND due to races or permission problems
1526
+ * that we want to quietly swallow
1527
+ */
1528
+ if (error == GIT_ENOTFOUND)
1529
+ continue;
1530
+ else if (error < 0)
1531
+ break;
1532
+ }
1452
1533
 
1453
- if (next != NULL)
1454
- break;
1534
+ if (!iterator__include_trees(iter))
1535
+ continue;
1536
+ }
1455
1537
 
1456
- fs_iterator__pop_frame(fi, ff, false);
1538
+ filesystem_iterator_set_current(iter, entry);
1539
+ break;
1457
1540
  }
1541
+
1542
+ if (out)
1543
+ *out = (error == 0) ? &iter->entry : NULL;
1544
+
1545
+ return error;
1458
1546
  }
1459
1547
 
1460
- static int fs_iterator__advance_over(
1461
- const git_index_entry **entry, git_iterator *self)
1548
+ static int filesystem_iterator_advance_into(
1549
+ const git_index_entry **out, git_iterator *i)
1462
1550
  {
1551
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1552
+ filesystem_iterator_frame *frame;
1553
+ filesystem_iterator_entry *prev_entry;
1463
1554
  int error;
1464
1555
 
1465
- if (entry != NULL)
1466
- *entry = NULL;
1556
+ if (out)
1557
+ *out = NULL;
1558
+
1559
+ if ((frame = filesystem_iterator_current_frame(iter)) == NULL)
1560
+ return GIT_ITEROVER;
1561
+
1562
+ /* get the last seen entry */
1563
+ prev_entry = filesystem_iterator_current_entry(frame);
1467
1564
 
1468
- fs_iterator__advance_over_internal(self);
1565
+ /* it's legal to call advance_into when auto-expand is on. in this case,
1566
+ * we will have pushed a new (empty) frame on to the stack for this
1567
+ * new directory. since it's empty, its current_entry should be null.
1568
+ */
1569
+ assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL));
1469
1570
 
1470
- error = fs_iterator__update_entry((fs_iterator *)self);
1571
+ if (prev_entry) {
1572
+ if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT &&
1573
+ !S_ISDIR(prev_entry->st.st_mode))
1574
+ return 0;
1471
1575
 
1472
- if (!error && entry != NULL)
1473
- error = fs_iterator__current(entry, self);
1576
+ if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0)
1577
+ return error;
1578
+ }
1474
1579
 
1475
- return error;
1580
+ /* we've advanced into the directory in question, let advance
1581
+ * find the first entry
1582
+ */
1583
+ return filesystem_iterator_advance(out, i);
1476
1584
  }
1477
1585
 
1478
- static int fs_iterator__advance(
1479
- const git_index_entry **entry, git_iterator *self)
1586
+ int git_iterator_current_workdir_path(git_buf **out, git_iterator *i)
1480
1587
  {
1481
- fs_iterator *fi = (fs_iterator *)self;
1482
-
1483
- if (!iterator__has_been_accessed(fi))
1484
- return fs_iterator__current(entry, self);
1588
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1589
+ const git_index_entry *entry;
1485
1590
 
1486
- /* given include_trees & autoexpand, we might have to go into a tree */
1487
- if (iterator__do_autoexpand(fi) &&
1488
- fi->entry.path != NULL &&
1489
- fi->entry.mode == GIT_FILEMODE_TREE)
1490
- {
1491
- int error = fs_iterator__advance_into(entry, self);
1492
- if (error != GIT_ENOTFOUND)
1493
- return error;
1494
- /* continue silently past empty directories if autoexpanding */
1495
- giterr_clear();
1591
+ if (i->type != GIT_ITERATOR_TYPE_FS &&
1592
+ i->type != GIT_ITERATOR_TYPE_WORKDIR) {
1593
+ *out = NULL;
1594
+ return 0;
1496
1595
  }
1497
1596
 
1498
- return fs_iterator__advance_over(entry, self);
1597
+ git_buf_truncate(&iter->current_path, iter->root_len);
1598
+
1599
+ if (git_iterator_current(&entry, i) < 0 ||
1600
+ git_buf_puts(&iter->current_path, entry->path) < 0)
1601
+ return -1;
1602
+
1603
+ *out = &iter->current_path;
1604
+ return 0;
1499
1605
  }
1500
1606
 
1501
- static int fs_iterator__seek(git_iterator *self, const char *prefix)
1607
+ GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry)
1502
1608
  {
1503
- GIT_UNUSED(self);
1504
- GIT_UNUSED(prefix);
1505
- /* pop stack until matching prefix */
1506
- /* find prefix item in current frame */
1507
- /* push subdirectories as deep as possible while matching */
1508
- return 0;
1609
+ #if defined(GIT_WIN32) && !defined(__MINGW32__)
1610
+ return (entry && entry->mode) ?
1611
+ (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) :
1612
+ GIT_DIR_FLAG_UNKNOWN;
1613
+ #else
1614
+ GIT_UNUSED(entry);
1615
+ return GIT_DIR_FLAG_UNKNOWN;
1616
+ #endif
1509
1617
  }
1510
1618
 
1511
- static int fs_iterator__reset(
1512
- git_iterator *self, const char *start, const char *end)
1619
+ static void filesystem_iterator_update_ignored(filesystem_iterator *iter)
1513
1620
  {
1514
- int error;
1515
- fs_iterator *fi = (fs_iterator *)self;
1621
+ filesystem_iterator_frame *frame;
1622
+ git_dir_flag dir_flag = entry_dir_flag(&iter->entry);
1516
1623
 
1517
- while (fi->stack != NULL && fi->stack->next != NULL)
1518
- fs_iterator__pop_frame(fi, fi->stack, false);
1519
- fi->depth = 0;
1624
+ if (git_ignore__lookup(&iter->current_is_ignored,
1625
+ &iter->ignores, iter->entry.path, dir_flag) < 0) {
1626
+ giterr_clear();
1627
+ iter->current_is_ignored = GIT_IGNORE_NOTFOUND;
1628
+ }
1520
1629
 
1521
- if ((error = iterator__reset_range(self, start, end)) < 0)
1522
- return error;
1630
+ /* use ignore from containing frame stack */
1631
+ if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) {
1632
+ frame = filesystem_iterator_current_frame(iter);
1633
+ iter->current_is_ignored = frame->is_ignored;
1634
+ }
1635
+ }
1523
1636
 
1524
- fs_iterator__seek_frame_start(fi, fi->stack);
1637
+ GIT_INLINE(bool) filesystem_iterator_current_is_ignored(
1638
+ filesystem_iterator *iter)
1639
+ {
1640
+ if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED)
1641
+ filesystem_iterator_update_ignored(iter);
1525
1642
 
1526
- error = fs_iterator__update_entry(fi);
1527
- if (error == GIT_ITEROVER)
1528
- error = 0;
1643
+ return (iter->current_is_ignored == GIT_IGNORE_TRUE);
1644
+ }
1529
1645
 
1530
- return error;
1646
+ bool git_iterator_current_is_ignored(git_iterator *i)
1647
+ {
1648
+ if (i->type != GIT_ITERATOR_TYPE_WORKDIR)
1649
+ return false;
1650
+
1651
+ return filesystem_iterator_current_is_ignored((filesystem_iterator *)i);
1531
1652
  }
1532
1653
 
1533
- static void fs_iterator__free(git_iterator *self)
1654
+ bool git_iterator_current_tree_is_ignored(git_iterator *i)
1534
1655
  {
1535
- fs_iterator *fi = (fs_iterator *)self;
1656
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1657
+ filesystem_iterator_frame *frame;
1536
1658
 
1537
- while (fi->stack != NULL)
1538
- fs_iterator__pop_frame(fi, fi->stack, true);
1659
+ if (i->type != GIT_ITERATOR_TYPE_WORKDIR)
1660
+ return false;
1539
1661
 
1540
- git_buf_free(&fi->path);
1662
+ frame = filesystem_iterator_current_frame(iter);
1663
+ return (frame->is_ignored == GIT_IGNORE_TRUE);
1541
1664
  }
1542
1665
 
1543
- static int fs_iterator__update_entry(fs_iterator *fi)
1666
+ static int filesystem_iterator_advance_over(
1667
+ const git_index_entry **out,
1668
+ git_iterator_status_t *status,
1669
+ git_iterator *i)
1544
1670
  {
1545
- fs_iterator_path_with_stat *ps;
1671
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1672
+ filesystem_iterator_frame *current_frame;
1673
+ filesystem_iterator_entry *current_entry;
1674
+ const git_index_entry *entry = NULL;
1675
+ const char *base;
1676
+ int error = 0;
1546
1677
 
1547
- while (true) {
1548
- memset(&fi->entry, 0, sizeof(fi->entry));
1678
+ *out = NULL;
1679
+ *status = GIT_ITERATOR_STATUS_NORMAL;
1549
1680
 
1550
- if (!fi->stack)
1551
- return GIT_ITEROVER;
1681
+ assert(iterator__has_been_accessed(i));
1552
1682
 
1553
- ps = git_vector_get(&fi->stack->entries, fi->stack->index);
1554
- if (!ps)
1555
- return GIT_ITEROVER;
1683
+ current_frame = filesystem_iterator_current_frame(iter);
1684
+ assert(current_frame);
1685
+ current_entry = filesystem_iterator_current_entry(current_frame);
1686
+ assert(current_entry);
1556
1687
 
1557
- git_buf_truncate(&fi->path, fi->root_len);
1558
- if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0)
1559
- return -1;
1688
+ if ((error = git_iterator_current(&entry, i)) < 0)
1689
+ return error;
1560
1690
 
1561
- if (iterator__past_end(fi, fi->path.ptr + fi->root_len))
1562
- return GIT_ITEROVER;
1691
+ if (!S_ISDIR(entry->mode)) {
1692
+ if (filesystem_iterator_current_is_ignored(iter))
1693
+ *status = GIT_ITERATOR_STATUS_IGNORED;
1563
1694
 
1564
- fi->entry.path = ps->path;
1565
- fi->pathlist_match = ps->pathlist_match;
1566
- git_index_entry__init_from_stat(&fi->entry, &ps->st, true);
1695
+ return filesystem_iterator_advance(out, i);
1696
+ }
1567
1697
 
1568
- /* need different mode here to keep directories during iteration */
1569
- fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
1698
+ git_buf_clear(&iter->tmp_buf);
1699
+ if ((error = git_buf_puts(&iter->tmp_buf, entry->path)) < 0)
1700
+ return error;
1570
1701
 
1571
- /* allow wrapper to check/update the entry (can force skip) */
1572
- if (fi->update_entry_cb &&
1573
- fi->update_entry_cb(fi) == GIT_ENOTFOUND) {
1574
- fs_iterator__advance_over_internal(&fi->base);
1575
- continue;
1576
- }
1702
+ base = iter->tmp_buf.ptr;
1703
+
1704
+ /* scan inside the directory looking for files. if we find nothing,
1705
+ * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to
1706
+ * IGNORED. if we find a real actual item, upgrade all the way to NORMAL
1707
+ * and then stop.
1708
+ *
1709
+ * however, if we're here looking for a pathlist item (but are not
1710
+ * actually in the pathlist ourselves) then start at FILTERED instead of
1711
+ * EMPTY. callers then know that this path was not something they asked
1712
+ * about.
1713
+ */
1714
+ *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ?
1715
+ GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY;
1716
+
1717
+ while (entry && !iter->base.prefixcomp(entry->path, base)) {
1718
+ if (filesystem_iterator_current_is_ignored(iter)) {
1719
+ /* if we found an explicitly ignored item, then update from
1720
+ * EMPTY to IGNORED
1721
+ */
1722
+ *status = GIT_ITERATOR_STATUS_IGNORED;
1723
+ } else if (S_ISDIR(entry->mode)) {
1724
+ error = filesystem_iterator_advance_into(&entry, i);
1577
1725
 
1578
- /* if this is a tree and trees aren't included, then skip */
1579
- if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) {
1580
- int error = fs_iterator__advance_into(NULL, &fi->base);
1726
+ if (!error)
1727
+ continue;
1581
1728
 
1582
- if (error != GIT_ENOTFOUND)
1583
- return error;
1729
+ /* this directory disappeared, ignore it */
1730
+ else if (error == GIT_ENOTFOUND)
1731
+ error = 0;
1584
1732
 
1585
- giterr_clear();
1586
- fs_iterator__advance_over_internal(&fi->base);
1587
- continue;
1733
+ /* a real error occurred */
1734
+ else
1735
+ break;
1736
+ } else {
1737
+ /* we found a non-ignored item, treat parent as untracked */
1738
+ *status = GIT_ITERATOR_STATUS_NORMAL;
1739
+ break;
1588
1740
  }
1589
1741
 
1590
- break;
1742
+ if ((error = git_iterator_advance(&entry, i)) < 0)
1743
+ break;
1591
1744
  }
1592
1745
 
1593
- return 0;
1594
- }
1595
-
1596
- static int fs_iterator__initialize(
1597
- git_iterator **out, fs_iterator *fi, const char *root)
1598
- {
1599
- int error;
1600
-
1601
- if (git_buf_sets(&fi->path, root) < 0 || git_path_to_dir(&fi->path) < 0) {
1602
- git__free(fi);
1603
- return -1;
1746
+ /* wrap up scan back to base directory */
1747
+ while (entry && !iter->base.prefixcomp(entry->path, base)) {
1748
+ if ((error = git_iterator_advance(&entry, i)) < 0)
1749
+ break;
1604
1750
  }
1605
- fi->root_len = fi->path.size;
1606
- fi->pathlist_match = ITERATOR_PATHLIST_MATCH_CHILD;
1607
-
1608
- fi->dirload_flags =
1609
- (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
1610
- (iterator__flag(fi, PRECOMPOSE_UNICODE) ?
1611
- GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0);
1612
1751
 
1613
- if ((error = fs_iterator__expand_dir(fi)) < 0) {
1614
- if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) {
1615
- giterr_clear();
1616
- error = 0;
1617
- } else {
1618
- git_iterator_free((git_iterator *)fi);
1619
- fi = NULL;
1620
- }
1621
- }
1752
+ if (!error)
1753
+ *out = entry;
1622
1754
 
1623
- *out = (git_iterator *)fi;
1624
1755
  return error;
1625
1756
  }
1626
1757
 
1627
- int git_iterator_for_filesystem(
1628
- git_iterator **out,
1629
- const char *root,
1630
- git_iterator_options *options)
1758
+ static void filesystem_iterator_clear(filesystem_iterator *iter)
1631
1759
  {
1632
- fs_iterator *fi = git__calloc(1, sizeof(fs_iterator));
1633
- GITERR_CHECK_ALLOC(fi);
1760
+ while (iter->frames.size)
1761
+ filesystem_iterator_frame_pop(iter);
1634
1762
 
1635
- ITERATOR_BASE_INIT(fi, fs, FS, NULL);
1763
+ git_array_clear(iter->frames);
1764
+ git_ignore__free(&iter->ignores);
1636
1765
 
1637
- if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0)
1638
- fi->base.flags |= GIT_ITERATOR_IGNORE_CASE;
1766
+ git_buf_free(&iter->tmp_buf);
1639
1767
 
1640
- return fs_iterator__initialize(out, fi, root);
1768
+ iterator_clear(&iter->base);
1641
1769
  }
1642
1770
 
1643
-
1644
- typedef struct {
1645
- fs_iterator fi;
1646
- git_ignores ignores;
1647
- int is_ignored;
1648
-
1649
- /*
1650
- * We may have a tree or the index+snapshot to compare against
1651
- * when checking for submodules.
1652
- */
1653
- git_tree *tree;
1654
- git_index *index;
1655
- git_vector index_snapshot;
1656
- git_vector_cmp entry_srch;
1657
-
1658
- } workdir_iterator;
1659
-
1660
- GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path)
1771
+ static int filesystem_iterator_init(filesystem_iterator *iter)
1661
1772
  {
1662
- size_t len;
1773
+ int error;
1663
1774
 
1664
- if (!path || (len = path->size) < 4)
1665
- return false;
1775
+ if (iterator__honor_ignores(&iter->base) &&
1776
+ (error = git_ignore__for_path(iter->base.repo,
1777
+ ".gitignore", &iter->ignores)) < 0)
1778
+ return error;
1666
1779
 
1667
- if (path->ptr[len - 1] == '/')
1668
- len--;
1780
+ if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0)
1781
+ return error;
1669
1782
 
1670
- if (git__tolower(path->ptr[len - 1]) != 't' ||
1671
- git__tolower(path->ptr[len - 2]) != 'i' ||
1672
- git__tolower(path->ptr[len - 3]) != 'g' ||
1673
- git__tolower(path->ptr[len - 4]) != '.')
1674
- return false;
1783
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
1675
1784
 
1676
- return (len == 4 || path->ptr[len - 5] == '/');
1785
+ return 0;
1677
1786
  }
1678
1787
 
1679
- /**
1680
- * Figure out if an entry is a submodule.
1681
- *
1682
- * We consider it a submodule if the path is listed as a submodule in
1683
- * either the tree or the index.
1684
- */
1685
- static int is_submodule(workdir_iterator *wi, fs_iterator_path_with_stat *ie)
1788
+ static int filesystem_iterator_reset(git_iterator *i)
1686
1789
  {
1687
- int error, is_submodule = 0;
1688
-
1689
- if (wi->tree) {
1690
- git_tree_entry *e;
1790
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1691
1791
 
1692
- /* remove the trailing slash for finding */
1693
- ie->path[ie->path_len-1] = '\0';
1694
- error = git_tree_entry_bypath(&e, wi->tree, ie->path);
1695
- ie->path[ie->path_len-1] = '/';
1696
- if (error < 0 && error != GIT_ENOTFOUND)
1697
- return 0;
1698
- if (!error) {
1699
- is_submodule = e->attr == GIT_FILEMODE_COMMIT;
1700
- git_tree_entry_free(e);
1701
- }
1702
- }
1703
-
1704
- if (!is_submodule && wi->index) {
1705
- git_index_entry *e;
1706
- size_t pos;
1707
-
1708
- error = git_index_snapshot_find(&pos, &wi->index_snapshot, wi->entry_srch, ie->path, ie->path_len-1, 0);
1709
- if (error < 0 && error != GIT_ENOTFOUND)
1710
- return 0;
1711
-
1712
- if (!error) {
1713
- e = git_vector_get(&wi->index_snapshot, pos);
1714
-
1715
- is_submodule = e->mode == GIT_FILEMODE_COMMIT;
1716
- }
1717
- }
1718
-
1719
- return is_submodule;
1792
+ filesystem_iterator_clear(iter);
1793
+ return filesystem_iterator_init(iter);
1720
1794
  }
1721
1795
 
1722
- GIT_INLINE(git_dir_flag) git_entry__dir_flag(git_index_entry *entry) {
1723
- #if defined(GIT_WIN32) && !defined(__MINGW32__)
1724
- return (entry && entry->mode)
1725
- ? S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE
1726
- : GIT_DIR_FLAG_UNKNOWN;
1727
- #else
1728
- GIT_UNUSED(entry);
1729
- return GIT_DIR_FLAG_UNKNOWN;
1730
- #endif
1796
+ static void filesystem_iterator_free(git_iterator *i)
1797
+ {
1798
+ filesystem_iterator *iter = (filesystem_iterator *)i;
1799
+ git__free(iter->root);
1800
+ git_buf_free(&iter->current_path);
1801
+ git_tree_free(iter->tree);
1802
+ if (iter->index)
1803
+ git_index_snapshot_release(&iter->index_snapshot, iter->index);
1804
+ filesystem_iterator_clear(iter);
1731
1805
  }
1732
1806
 
1733
- static int workdir_iterator__enter_dir(fs_iterator *fi)
1807
+ static int iterator_for_filesystem(
1808
+ git_iterator **out,
1809
+ git_repository *repo,
1810
+ const char *root,
1811
+ git_index *index,
1812
+ git_tree *tree,
1813
+ git_iterator_type_t type,
1814
+ git_iterator_options *options)
1734
1815
  {
1735
- workdir_iterator *wi = (workdir_iterator *)fi;
1736
- fs_iterator_frame *ff = fi->stack;
1737
- size_t pos;
1738
- fs_iterator_path_with_stat *entry;
1739
- bool found_submodules = false;
1816
+ filesystem_iterator *iter;
1817
+ size_t root_len;
1818
+ int error;
1740
1819
 
1741
- git_dir_flag dir_flag = git_entry__dir_flag(&fi->entry);
1820
+ static git_iterator_callbacks callbacks = {
1821
+ filesystem_iterator_current,
1822
+ filesystem_iterator_advance,
1823
+ filesystem_iterator_advance_into,
1824
+ filesystem_iterator_advance_over,
1825
+ filesystem_iterator_reset,
1826
+ filesystem_iterator_free
1827
+ };
1742
1828
 
1743
- /* check if this directory is ignored */
1744
- if (git_ignore__lookup(&ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len, dir_flag) < 0) {
1745
- giterr_clear();
1746
- ff->is_ignored = GIT_IGNORE_NOTFOUND;
1747
- }
1829
+ *out = NULL;
1748
1830
 
1749
- /* if this is not the top level directory... */
1750
- if (ff->next != NULL) {
1751
- ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/');
1831
+ if (root == NULL)
1832
+ return git_iterator_for_nothing(out, options);
1752
1833
 
1753
- /* inherit ignored from parent if no rule specified */
1754
- if (ff->is_ignored <= GIT_IGNORE_NOTFOUND)
1755
- ff->is_ignored = ff->next->is_ignored;
1834
+ iter = git__calloc(1, sizeof(filesystem_iterator));
1835
+ GITERR_CHECK_ALLOC(iter);
1756
1836
 
1757
- /* push new ignores for files in this directory */
1758
- (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]);
1759
- }
1837
+ iter->base.type = type;
1838
+ iter->base.cb = &callbacks;
1760
1839
 
1761
- /* convert submodules to GITLINK and remove trailing slashes */
1762
- git_vector_foreach(&ff->entries, pos, entry) {
1763
- if (!S_ISDIR(entry->st.st_mode) || !strcmp(GIT_DIR, entry->path))
1764
- continue;
1840
+ root_len = strlen(root);
1765
1841
 
1766
- if (is_submodule(wi, entry)) {
1767
- entry->st.st_mode = GIT_FILEMODE_COMMIT;
1768
- entry->path_len--;
1769
- entry->path[entry->path_len] = '\0';
1770
- found_submodules = true;
1771
- }
1772
- }
1842
+ iter->root = git__malloc(root_len+2);
1843
+ GITERR_CHECK_ALLOC(iter->root);
1844
+
1845
+ memcpy(iter->root, root, root_len);
1773
1846
 
1774
- /* if we renamed submodules, re-sort and re-seek to start */
1775
- if (found_submodules) {
1776
- git_vector_set_sorted(&ff->entries, 0);
1777
- git_vector_sort(&ff->entries);
1778
- fs_iterator__seek_frame_start(fi, ff);
1847
+ if (root_len == 0 || root[root_len-1] != '/') {
1848
+ iter->root[root_len] = '/';
1849
+ root_len++;
1779
1850
  }
1851
+ iter->root[root_len] = '\0';
1852
+ iter->root_len = root_len;
1780
1853
 
1781
- return 0;
1782
- }
1854
+ if ((error = git_buf_puts(&iter->current_path, iter->root)) < 0)
1855
+ goto on_error;
1783
1856
 
1784
- static int workdir_iterator__leave_dir(fs_iterator *fi)
1785
- {
1786
- workdir_iterator *wi = (workdir_iterator *)fi;
1787
- git_ignore__pop_dir(&wi->ignores);
1788
- return 0;
1789
- }
1857
+ if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0)
1858
+ goto on_error;
1790
1859
 
1791
- static int workdir_iterator__update_entry(fs_iterator *fi)
1792
- {
1793
- workdir_iterator *wi = (workdir_iterator *)fi;
1860
+ if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0)
1861
+ goto on_error;
1862
+
1863
+ if (index &&
1864
+ (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0)
1865
+ goto on_error;
1794
1866
 
1795
- /* skip over .git entries */
1796
- if (workdir_path_is_dotgit(&fi->path))
1797
- return GIT_ENOTFOUND;
1867
+ iter->index = index;
1868
+ iter->dirload_flags =
1869
+ (iterator__ignore_case(&iter->base) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
1870
+ (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ?
1871
+ GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0);
1798
1872
 
1799
- /* reset is_ignored since we haven't checked yet */
1800
- wi->is_ignored = GIT_IGNORE_UNCHECKED;
1873
+ if ((error = filesystem_iterator_init(iter)) < 0)
1874
+ goto on_error;
1801
1875
 
1876
+ *out = &iter->base;
1802
1877
  return 0;
1878
+
1879
+ on_error:
1880
+ git_iterator_free(&iter->base);
1881
+ return error;
1803
1882
  }
1804
1883
 
1805
- static void workdir_iterator__free(git_iterator *self)
1884
+ int git_iterator_for_filesystem(
1885
+ git_iterator **out,
1886
+ const char *root,
1887
+ git_iterator_options *options)
1806
1888
  {
1807
- workdir_iterator *wi = (workdir_iterator *)self;
1808
- if (wi->index)
1809
- git_index_snapshot_release(&wi->index_snapshot, wi->index);
1810
- git_tree_free(wi->tree);
1811
- fs_iterator__free(self);
1812
- git_ignore__free(&wi->ignores);
1889
+ return iterator_for_filesystem(out,
1890
+ NULL, root, NULL, NULL, GIT_ITERATOR_TYPE_FS, options);
1813
1891
  }
1814
1892
 
1815
1893
  int git_iterator_for_workdir_ext(
@@ -1818,299 +1896,326 @@ int git_iterator_for_workdir_ext(
1818
1896
  const char *repo_workdir,
1819
1897
  git_index *index,
1820
1898
  git_tree *tree,
1821
- git_iterator_options *options)
1899
+ git_iterator_options *given_opts)
1822
1900
  {
1823
- int error, precompose = 0;
1824
- workdir_iterator *wi;
1901
+ git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT;
1825
1902
 
1826
1903
  if (!repo_workdir) {
1827
1904
  if (git_repository__ensure_not_bare(repo, "scan working directory") < 0)
1828
1905
  return GIT_EBAREREPO;
1829
- repo_workdir = git_repository_workdir(repo);
1830
- }
1831
1906
 
1832
- /* initialize as an fs iterator then do overrides */
1833
- wi = git__calloc(1, sizeof(workdir_iterator));
1834
- GITERR_CHECK_ALLOC(wi);
1835
- ITERATOR_BASE_INIT((&wi->fi), fs, FS, repo);
1836
-
1837
- wi->fi.base.type = GIT_ITERATOR_TYPE_WORKDIR;
1838
- wi->fi.cb.free = workdir_iterator__free;
1839
- wi->fi.enter_dir_cb = workdir_iterator__enter_dir;
1840
- wi->fi.leave_dir_cb = workdir_iterator__leave_dir;
1841
- wi->fi.update_entry_cb = workdir_iterator__update_entry;
1842
-
1843
- if ((error = iterator__update_ignore_case((git_iterator *)wi, options ? options->flags : 0)) < 0 ||
1844
- (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0)
1845
- {
1846
- git_iterator_free((git_iterator *)wi);
1847
- return error;
1907
+ repo_workdir = git_repository_workdir(repo);
1848
1908
  }
1849
1909
 
1850
- if (tree && (error = git_object_dup((git_object **)&wi->tree, (git_object *)tree)) < 0)
1851
- return error;
1852
-
1853
- wi->index = index;
1854
- if (index && (error = git_index_snapshot_new(&wi->index_snapshot, index)) < 0) {
1855
- git_iterator_free((git_iterator *)wi);
1856
- return error;
1857
- }
1858
- wi->entry_srch = iterator__ignore_case(wi) ?
1859
- git_index_entry_isrch : git_index_entry_srch;
1910
+ /* upgrade to a workdir iterator, adding necessary internal flags */
1911
+ if (given_opts)
1912
+ memcpy(&options, given_opts, sizeof(git_iterator_options));
1860
1913
 
1914
+ options.flags |= GIT_ITERATOR_HONOR_IGNORES |
1915
+ GIT_ITERATOR_IGNORE_DOT_GIT;
1861
1916
 
1862
- /* try to look up precompose and set flag if appropriate */
1863
- if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
1864
- giterr_clear();
1865
- else if (precompose)
1866
- wi->fi.base.flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
1867
-
1868
- return fs_iterator__initialize(out, &wi->fi, repo_workdir);
1917
+ return iterator_for_filesystem(out,
1918
+ repo, repo_workdir, index, tree, GIT_ITERATOR_TYPE_WORKDIR, &options);
1869
1919
  }
1870
1920
 
1871
- void git_iterator_free(git_iterator *iter)
1872
- {
1873
- if (iter == NULL)
1874
- return;
1875
1921
 
1876
- iter->cb->free(iter);
1922
+ /* Index iterator */
1877
1923
 
1878
- git_vector_free(&iter->pathlist);
1879
- git__free(iter->start);
1880
- git__free(iter->end);
1881
1924
 
1882
- memset(iter, 0, sizeof(*iter));
1925
+ typedef struct {
1926
+ git_iterator base;
1927
+ git_vector entries;
1928
+ size_t next_idx;
1883
1929
 
1884
- git__free(iter);
1885
- }
1930
+ /* the pseudotree entry */
1931
+ git_index_entry tree_entry;
1932
+ git_buf tree_buf;
1933
+ bool skip_tree;
1934
+
1935
+ const git_index_entry *entry;
1936
+ } index_iterator;
1886
1937
 
1887
- int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
1938
+ static int index_iterator_current(
1939
+ const git_index_entry **out, git_iterator *i)
1888
1940
  {
1889
- bool desire_ignore_case = (ignore_case != 0);
1941
+ index_iterator *iter = (index_iterator *)i;
1890
1942
 
1891
- if (iterator__ignore_case(iter) == desire_ignore_case)
1892
- return 0;
1943
+ if (!iterator__has_been_accessed(i))
1944
+ return iter->base.cb->advance(out, i);
1893
1945
 
1894
- if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {
1895
- if (desire_ignore_case)
1896
- iter->flags |= GIT_ITERATOR_IGNORE_CASE;
1897
- else
1898
- iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
1899
- } else {
1900
- giterr_set(GITERR_INVALID,
1901
- "Cannot currently set ignore case on non-empty iterators");
1902
- return -1;
1946
+ if (iter->entry == NULL) {
1947
+ *out = NULL;
1948
+ return GIT_ITEROVER;
1903
1949
  }
1904
1950
 
1951
+ *out = iter->entry;
1905
1952
  return 0;
1906
1953
  }
1907
1954
 
1908
- git_index *git_iterator_get_index(git_iterator *iter)
1955
+ static bool index_iterator_create_pseudotree(
1956
+ const git_index_entry **out,
1957
+ index_iterator *iter,
1958
+ const char *path)
1909
1959
  {
1910
- if (iter->type == GIT_ITERATOR_TYPE_INDEX)
1911
- return ((index_iterator *)iter)->index;
1912
- return NULL;
1913
- }
1960
+ const char *prev_path, *relative_path, *dirsep;
1961
+ size_t common_len;
1914
1962
 
1915
- int git_iterator_current_tree_entry(
1916
- const git_tree_entry **tree_entry, git_iterator *iter)
1917
- {
1918
- if (iter->type != GIT_ITERATOR_TYPE_TREE)
1919
- *tree_entry = NULL;
1920
- else {
1921
- tree_iterator_frame *tf = ((tree_iterator *)iter)->head;
1922
- *tree_entry = (tf->current < tf->n_entries) ?
1923
- tf->entries[tf->current]->te : NULL;
1924
- }
1963
+ prev_path = iter->entry ? iter->entry->path : "";
1925
1964
 
1926
- return 0;
1965
+ /* determine if the new path is in a different directory from the old */
1966
+ common_len = git_path_common_dirlen(prev_path, path);
1967
+ relative_path = path + common_len;
1968
+
1969
+ if ((dirsep = strchr(relative_path, '/')) == NULL)
1970
+ return false;
1971
+
1972
+ git_buf_clear(&iter->tree_buf);
1973
+ git_buf_put(&iter->tree_buf, path, (dirsep - path) + 1);
1974
+
1975
+ iter->tree_entry.mode = GIT_FILEMODE_TREE;
1976
+ iter->tree_entry.path = iter->tree_buf.ptr;
1977
+
1978
+ *out = &iter->tree_entry;
1979
+ return true;
1927
1980
  }
1928
1981
 
1929
- int git_iterator_current_parent_tree(
1930
- const git_tree **tree_ptr,
1931
- git_iterator *iter,
1932
- const char *parent_path)
1982
+ static int index_iterator_skip_pseudotree(index_iterator *iter)
1933
1983
  {
1934
- tree_iterator *ti = (tree_iterator *)iter;
1935
- tree_iterator_frame *tf;
1936
- const char *scan = parent_path;
1937
- const git_tree_entry *te;
1984
+ assert(iterator__has_been_accessed(&iter->base));
1985
+ assert(S_ISDIR(iter->entry->mode));
1938
1986
 
1939
- *tree_ptr = NULL;
1987
+ while (true) {
1988
+ const git_index_entry *next_entry = NULL;
1940
1989
 
1941
- if (iter->type != GIT_ITERATOR_TYPE_TREE)
1942
- return 0;
1990
+ if (++iter->next_idx >= iter->entries.length)
1991
+ return GIT_ITEROVER;
1943
1992
 
1944
- for (tf = ti->root; *scan; ) {
1945
- if (!(tf = tf->down) ||
1946
- tf->current >= tf->n_entries ||
1947
- !(te = tf->entries[tf->current]->te) ||
1948
- ti->base.strncomp(scan, te->filename, te->filename_len) != 0)
1949
- return 0;
1993
+ next_entry = iter->entries.contents[iter->next_idx];
1950
1994
 
1951
- scan += te->filename_len;
1952
- if (*scan == '/')
1953
- scan++;
1995
+ if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path,
1996
+ iter->tree_buf.size) != 0)
1997
+ break;
1954
1998
  }
1955
1999
 
1956
- *tree_ptr = tf->entries[tf->current]->tree;
2000
+ iter->skip_tree = false;
1957
2001
  return 0;
1958
2002
  }
1959
2003
 
1960
- static void workdir_iterator_update_is_ignored(workdir_iterator *wi)
2004
+ static int index_iterator_advance(
2005
+ const git_index_entry **out, git_iterator *i)
1961
2006
  {
1962
- git_dir_flag dir_flag = git_entry__dir_flag(&wi->fi.entry);
2007
+ index_iterator *iter = (index_iterator *)i;
2008
+ const git_index_entry *entry = NULL;
2009
+ bool is_submodule;
2010
+ int error = 0;
1963
2011
 
1964
- if (git_ignore__lookup(&wi->is_ignored, &wi->ignores, wi->fi.entry.path, dir_flag) < 0) {
1965
- giterr_clear();
1966
- wi->is_ignored = GIT_IGNORE_NOTFOUND;
1967
- }
2012
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
1968
2013
 
1969
- /* use ignore from containing frame stack */
1970
- if (wi->is_ignored <= GIT_IGNORE_NOTFOUND)
1971
- wi->is_ignored = wi->fi.stack->is_ignored;
1972
- }
2014
+ while (true) {
2015
+ if (iter->next_idx >= iter->entries.length) {
2016
+ error = GIT_ITEROVER;
2017
+ break;
2018
+ }
1973
2019
 
1974
- bool git_iterator_current_is_ignored(git_iterator *iter)
1975
- {
1976
- workdir_iterator *wi = (workdir_iterator *)iter;
2020
+ /* we were not asked to expand this pseudotree. advance over it. */
2021
+ if (iter->skip_tree) {
2022
+ index_iterator_skip_pseudotree(iter);
2023
+ continue;
2024
+ }
1977
2025
 
1978
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
1979
- return false;
2026
+ entry = iter->entries.contents[iter->next_idx];
2027
+ is_submodule = S_ISGITLINK(entry->mode);
1980
2028
 
1981
- if (wi->is_ignored != GIT_IGNORE_UNCHECKED)
1982
- return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
2029
+ if (!iterator_has_started(&iter->base, entry->path, is_submodule)) {
2030
+ iter->next_idx++;
2031
+ continue;
2032
+ }
1983
2033
 
1984
- workdir_iterator_update_is_ignored(wi);
2034
+ if (iterator_has_ended(&iter->base, entry->path)) {
2035
+ error = GIT_ITEROVER;
2036
+ break;
2037
+ }
2038
+
2039
+ /* if we have a list of paths we're interested in, examine it */
2040
+ if (!iterator_pathlist_next_is(&iter->base, entry->path)) {
2041
+ iter->next_idx++;
2042
+ continue;
2043
+ }
2044
+
2045
+ /* if this is a conflict, skip it unless we're including conflicts */
2046
+ if (git_index_entry_is_conflict(entry) &&
2047
+ !iterator__include_conflicts(&iter->base)) {
2048
+ iter->next_idx++;
2049
+ continue;
2050
+ }
2051
+
2052
+ /* we've found what will be our next _file_ entry. but if we are
2053
+ * returning trees entries, we may need to return a pseudotree
2054
+ * entry that will contain this. don't advance over this entry,
2055
+ * though, we still need to return it on the next `advance`.
2056
+ */
2057
+ if (iterator__include_trees(&iter->base) &&
2058
+ index_iterator_create_pseudotree(&entry, iter, entry->path)) {
2059
+
2060
+ /* Note whether this pseudo tree should be expanded or not */
2061
+ iter->skip_tree = iterator__dont_autoexpand(&iter->base);
2062
+ break;
2063
+ }
1985
2064
 
1986
- return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
2065
+ iter->next_idx++;
2066
+ break;
2067
+ }
2068
+
2069
+ iter->entry = (error == 0) ? entry : NULL;
2070
+
2071
+ if (out)
2072
+ *out = iter->entry;
2073
+
2074
+ return error;
1987
2075
  }
1988
2076
 
1989
- bool git_iterator_current_tree_is_ignored(git_iterator *iter)
2077
+ static int index_iterator_advance_into(
2078
+ const git_index_entry **out, git_iterator *i)
1990
2079
  {
1991
- workdir_iterator *wi = (workdir_iterator *)iter;
2080
+ index_iterator *iter = (index_iterator *)i;
1992
2081
 
1993
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
1994
- return false;
2082
+ if (! S_ISDIR(iter->tree_entry.mode)) {
2083
+ if (out)
2084
+ *out = NULL;
2085
+
2086
+ return 0;
2087
+ }
1995
2088
 
1996
- return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE);
2089
+ iter->skip_tree = false;
2090
+ return index_iterator_advance(out, i);
1997
2091
  }
1998
2092
 
1999
- int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
2093
+ static int index_iterator_advance_over(
2094
+ const git_index_entry **out,
2095
+ git_iterator_status_t *status,
2096
+ git_iterator *i)
2000
2097
  {
2098
+ index_iterator *iter = (index_iterator *)i;
2001
2099
  const git_index_entry *entry;
2100
+ int error;
2002
2101
 
2003
- /* a "done" iterator is after every prefix */
2004
- if (git_iterator_current(&entry, iter) < 0 || entry == NULL)
2005
- return 1;
2102
+ if ((error = index_iterator_current(&entry, i)) < 0)
2103
+ return error;
2006
2104
 
2007
- /* a NULL prefix is after any valid iterator */
2008
- if (!path_prefix)
2009
- return -1;
2105
+ if (S_ISDIR(entry->mode))
2106
+ index_iterator_skip_pseudotree(iter);
2010
2107
 
2011
- return iter->prefixcomp(entry->path, path_prefix);
2108
+ *status = GIT_ITERATOR_STATUS_NORMAL;
2109
+ return index_iterator_advance(out, i);
2012
2110
  }
2013
2111
 
2014
- int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
2112
+ static void index_iterator_clear(index_iterator *iter)
2015
2113
  {
2016
- workdir_iterator *wi = (workdir_iterator *)iter;
2017
-
2018
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->fi.entry.path)
2019
- *path = NULL;
2020
- else
2021
- *path = &wi->fi.path;
2114
+ iterator_clear(&iter->base);
2115
+ }
2022
2116
 
2117
+ static int index_iterator_init(index_iterator *iter)
2118
+ {
2119
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
2120
+ iter->next_idx = 0;
2121
+ iter->skip_tree = false;
2023
2122
  return 0;
2024
2123
  }
2025
2124
 
2026
- int git_iterator_index(git_index **out, git_iterator *iter)
2125
+ static int index_iterator_reset(git_iterator *i)
2027
2126
  {
2028
- workdir_iterator *wi = (workdir_iterator *)iter;
2127
+ index_iterator *iter = (index_iterator *)i;
2029
2128
 
2030
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
2031
- *out = NULL;
2129
+ index_iterator_clear(iter);
2130
+ return index_iterator_init(iter);
2131
+ }
2032
2132
 
2033
- *out = wi->index;
2133
+ static void index_iterator_free(git_iterator *i)
2134
+ {
2135
+ index_iterator *iter = (index_iterator *)i;
2034
2136
 
2035
- return 0;
2137
+ git_index_snapshot_release(&iter->entries, iter->base.index);
2138
+ git_buf_free(&iter->tree_buf);
2036
2139
  }
2037
2140
 
2038
- int git_iterator_advance_over_with_status(
2039
- const git_index_entry **entryptr,
2040
- git_iterator_status_t *status,
2041
- git_iterator *iter)
2141
+ int git_iterator_for_index(
2142
+ git_iterator **out,
2143
+ git_repository *repo,
2144
+ git_index *index,
2145
+ git_iterator_options *options)
2042
2146
  {
2043
- int error = 0;
2044
- workdir_iterator *wi = (workdir_iterator *)iter;
2045
- char *base = NULL;
2046
- const git_index_entry *entry;
2147
+ index_iterator *iter;
2148
+ int error;
2047
2149
 
2048
- *status = GIT_ITERATOR_STATUS_NORMAL;
2150
+ static git_iterator_callbacks callbacks = {
2151
+ index_iterator_current,
2152
+ index_iterator_advance,
2153
+ index_iterator_advance_into,
2154
+ index_iterator_advance_over,
2155
+ index_iterator_reset,
2156
+ index_iterator_free
2157
+ };
2049
2158
 
2050
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
2051
- return git_iterator_advance(entryptr, iter);
2052
- if ((error = git_iterator_current(&entry, iter)) < 0)
2053
- return error;
2159
+ *out = NULL;
2054
2160
 
2055
- if (!S_ISDIR(entry->mode)) {
2056
- workdir_iterator_update_is_ignored(wi);
2057
- if (wi->is_ignored == GIT_IGNORE_TRUE)
2058
- *status = GIT_ITERATOR_STATUS_IGNORED;
2059
- return git_iterator_advance(entryptr, iter);
2060
- }
2161
+ if (index == NULL)
2162
+ return git_iterator_for_nothing(out, options);
2061
2163
 
2062
- *status = GIT_ITERATOR_STATUS_EMPTY;
2164
+ iter = git__calloc(1, sizeof(index_iterator));
2165
+ GITERR_CHECK_ALLOC(iter);
2063
2166
 
2064
- base = git__strdup(entry->path);
2065
- GITERR_CHECK_ALLOC(base);
2167
+ iter->base.type = GIT_ITERATOR_TYPE_INDEX;
2168
+ iter->base.cb = &callbacks;
2066
2169
 
2067
- /* scan inside directory looking for a non-ignored item */
2068
- while (entry && !iter->prefixcomp(entry->path, base)) {
2069
- workdir_iterator_update_is_ignored(wi);
2170
+ if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 ||
2171
+ (error = git_index_snapshot_new(&iter->entries, index)) < 0 ||
2172
+ (error = index_iterator_init(iter)) < 0)
2173
+ goto on_error;
2070
2174
 
2071
- /* if we found an explicitly ignored item, then update from
2072
- * EMPTY to IGNORED
2073
- */
2074
- if (wi->is_ignored == GIT_IGNORE_TRUE)
2075
- *status = GIT_ITERATOR_STATUS_IGNORED;
2076
- else if (S_ISDIR(entry->mode)) {
2077
- error = git_iterator_advance_into(&entry, iter);
2175
+ git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ?
2176
+ git_index_entry_icmp : git_index_entry_cmp);
2177
+ git_vector_sort(&iter->entries);
2078
2178
 
2079
- if (!error)
2080
- continue;
2179
+ *out = &iter->base;
2180
+ return 0;
2081
2181
 
2082
- else if (error == GIT_ENOTFOUND) {
2083
- /* we entered this directory only hoping to find child matches to
2084
- * our pathlist (eg, this is `foo` and we had a pathlist entry for
2085
- * `foo/bar`). it should not be ignored, it should be excluded.
2086
- */
2087
- if (wi->fi.pathlist_match == ITERATOR_PATHLIST_MATCH_CHILD)
2088
- *status = GIT_ITERATOR_STATUS_FILTERED;
2089
- else
2090
- wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */
2182
+ on_error:
2183
+ git_iterator_free(&iter->base);
2184
+ return error;
2185
+ }
2091
2186
 
2092
- error = 0;
2093
- } else
2094
- break; /* real error, stop here */
2095
- } else {
2096
- /* we found a non-ignored item, treat parent as untracked */
2097
- *status = GIT_ITERATOR_STATUS_NORMAL;
2098
- break;
2099
- }
2100
2187
 
2101
- if ((error = git_iterator_advance(&entry, iter)) < 0)
2102
- break;
2103
- }
2188
+ /* Iterator API */
2104
2189
 
2105
- /* wrap up scan back to base directory */
2106
- while (entry && !iter->prefixcomp(entry->path, base))
2107
- if ((error = git_iterator_advance(&entry, iter)) < 0)
2108
- break;
2190
+ int git_iterator_reset_range(
2191
+ git_iterator *i, const char *start, const char *end)
2192
+ {
2193
+ if (iterator_reset_range(i, start, end) < 0)
2194
+ return -1;
2109
2195
 
2110
- *entryptr = entry;
2111
- git__free(base);
2196
+ return i->cb->reset(i);
2197
+ }
2112
2198
 
2113
- return error;
2199
+ void git_iterator_set_ignore_case(git_iterator *i, bool ignore_case)
2200
+ {
2201
+ assert(!iterator__has_been_accessed(i));
2202
+ iterator_set_ignore_case(i, ignore_case);
2203
+ }
2204
+
2205
+ void git_iterator_free(git_iterator *iter)
2206
+ {
2207
+ if (iter == NULL)
2208
+ return;
2209
+
2210
+ iter->cb->free(iter);
2211
+
2212
+ git_vector_free(&iter->pathlist);
2213
+ git__free(iter->start);
2214
+ git__free(iter->end);
2215
+
2216
+ memset(iter, 0, sizeof(*iter));
2217
+
2218
+ git__free(iter);
2114
2219
  }
2115
2220
 
2116
2221
  int git_iterator_walk(