]>
Commit | Line | Data |
---|---|---|
b6c93aef | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
b6c93aef RB |
3 | * |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
8 | #include "iterator.h" | |
eae0bfdc | 9 | |
b6c93aef | 10 | #include "tree.h" |
114f5a6c | 11 | #include "index.h" |
b6c93aef | 12 | |
0e0589fc ET |
13 | #define GIT_ITERATOR_FIRST_ACCESS (1 << 15) |
14 | #define GIT_ITERATOR_HONOR_IGNORES (1 << 16) | |
15 | #define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17) | |
169dc616 | 16 | |
0e0589fc ET |
17 | #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) |
18 | #define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) | |
19 | #define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) | |
20 | #define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) | |
21 | #define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) | |
22 | #define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS) | |
cee695ae | 23 | #define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) |
0e0589fc ET |
24 | #define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES) |
25 | #define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) | |
eae0bfdc | 26 | #define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS) |
cee695ae | 27 | |
4a0dbeb0 | 28 | |
9eb9e5fa | 29 | static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) |
d53c8880 | 30 | { |
9eb9e5fa ET |
31 | if (ignore_case) |
32 | iter->flags |= GIT_ITERATOR_IGNORE_CASE; | |
33 | else | |
34 | iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; | |
d53c8880 | 35 | |
9eb9e5fa ET |
36 | iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp; |
37 | iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp; | |
38 | iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp; | |
4df6ddaa | 39 | iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch; |
d53c8880 | 40 | |
d53c8880 | 41 | git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); |
91e7d263 | 42 | } |
41a82592 | 43 | |
be30387e ET |
44 | static int iterator_range_init( |
45 | git_iterator *iter, const char *start, const char *end) | |
46 | { | |
0e0589fc | 47 | if (start && *start) { |
be30387e | 48 | iter->start = git__strdup(start); |
ac3d33df | 49 | GIT_ERROR_CHECK_ALLOC(iter->start); |
0e0589fc ET |
50 | |
51 | iter->start_len = strlen(iter->start); | |
be30387e ET |
52 | } |
53 | ||
0e0589fc | 54 | if (end && *end) { |
be30387e | 55 | iter->end = git__strdup(end); |
ac3d33df | 56 | GIT_ERROR_CHECK_ALLOC(iter->end); |
0e0589fc ET |
57 | |
58 | iter->end_len = strlen(iter->end); | |
be30387e ET |
59 | } |
60 | ||
61 | iter->started = (iter->start == NULL); | |
62 | iter->ended = false; | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
67 | static void iterator_range_free(git_iterator *iter) | |
68 | { | |
69 | if (iter->start) { | |
70 | git__free(iter->start); | |
71 | iter->start = NULL; | |
0e0589fc | 72 | iter->start_len = 0; |
be30387e ET |
73 | } |
74 | ||
75 | if (iter->end) { | |
76 | git__free(iter->end); | |
77 | iter->end = NULL; | |
0e0589fc | 78 | iter->end_len = 0; |
be30387e ET |
79 | } |
80 | } | |
81 | ||
9eb9e5fa | 82 | static int iterator_reset_range( |
be30387e ET |
83 | git_iterator *iter, const char *start, const char *end) |
84 | { | |
85 | iterator_range_free(iter); | |
86 | return iterator_range_init(iter, start, end); | |
87 | } | |
88 | ||
89 | static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist) | |
90 | { | |
91 | size_t i; | |
92 | ||
9eb9e5fa | 93 | if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0) |
be30387e ET |
94 | return -1; |
95 | ||
96 | for (i = 0; i < pathlist->count; i++) { | |
97 | if (!pathlist->strings[i]) | |
98 | continue; | |
99 | ||
100 | if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0) | |
101 | return -1; | |
102 | } | |
103 | ||
be30387e ET |
104 | return 0; |
105 | } | |
106 | ||
107 | static int iterator_init_common( | |
108 | git_iterator *iter, | |
109 | git_repository *repo, | |
0ef0b71c | 110 | git_index *index, |
be30387e ET |
111 | git_iterator_options *given_opts) |
112 | { | |
113 | static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT; | |
114 | git_iterator_options *options = given_opts ? given_opts : &default_opts; | |
115 | bool ignore_case; | |
0e0589fc | 116 | int precompose; |
be30387e ET |
117 | int error; |
118 | ||
be30387e | 119 | iter->repo = repo; |
0ef0b71c | 120 | iter->index = index; |
be30387e ET |
121 | iter->flags = options->flags; |
122 | ||
123 | if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) { | |
124 | ignore_case = true; | |
125 | } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) { | |
126 | ignore_case = false; | |
0e0589fc | 127 | } else if (repo) { |
be30387e ET |
128 | git_index *index; |
129 | ||
130 | if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) | |
131 | return error; | |
132 | ||
133 | ignore_case = !!index->ignore_case; | |
134 | ||
135 | if (ignore_case == 1) | |
136 | iter->flags |= GIT_ITERATOR_IGNORE_CASE; | |
137 | else | |
138 | iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE; | |
0e0589fc ET |
139 | } else { |
140 | ignore_case = false; | |
141 | } | |
142 | ||
143 | /* try to look up precompose and set flag if appropriate */ | |
144 | if (repo && | |
145 | (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 && | |
146 | (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) { | |
147 | ||
0c9c969a | 148 | if (git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) < 0) |
ac3d33df | 149 | git_error_clear(); |
0e0589fc ET |
150 | else if (precompose) |
151 | iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; | |
be30387e ET |
152 | } |
153 | ||
154 | if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND)) | |
155 | iter->flags |= GIT_ITERATOR_INCLUDE_TREES; | |
156 | ||
be30387e ET |
157 | if ((error = iterator_range_init(iter, options->start, options->end)) < 0 || |
158 | (error = iterator_pathlist_init(iter, &options->pathlist)) < 0) | |
159 | return error; | |
160 | ||
9eb9e5fa | 161 | iterator_set_ignore_case(iter, ignore_case); |
be30387e ET |
162 | return 0; |
163 | } | |
164 | ||
165 | static void iterator_clear(git_iterator *iter) | |
166 | { | |
167 | iter->started = false; | |
168 | iter->ended = false; | |
0e0589fc | 169 | iter->stat_calls = 0; |
be30387e ET |
170 | iter->pathlist_walk_idx = 0; |
171 | iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS; | |
172 | } | |
173 | ||
d47f7e1c ET |
174 | GIT_INLINE(bool) iterator_has_started( |
175 | git_iterator *iter, const char *path, bool is_submodule) | |
be30387e ET |
176 | { |
177 | size_t path_len; | |
178 | ||
179 | if (iter->start == NULL || iter->started == true) | |
180 | return true; | |
181 | ||
182 | /* the starting path is generally a prefix - we have started once we | |
183 | * are prefixed by this path | |
184 | */ | |
185 | iter->started = (iter->prefixcomp(path, iter->start) >= 0); | |
186 | ||
d47f7e1c ET |
187 | if (iter->started) |
188 | return true; | |
189 | ||
190 | path_len = strlen(path); | |
191 | ||
192 | /* if, however, we are a submodule, then we support `start` being | |
193 | * suffixed with a `/` for crazy legacy reasons. match `submod` | |
194 | * with a start path of `submod/`. | |
195 | */ | |
196 | if (is_submodule && iter->start_len && path_len == iter->start_len - 1 && | |
197 | iter->start[iter->start_len-1] == '/') | |
198 | return true; | |
199 | ||
be30387e ET |
200 | /* if, however, our current path is a directory, and our starting path |
201 | * is _beneath_ that directory, then recurse into the directory (even | |
202 | * though we have not yet "started") | |
203 | */ | |
d47f7e1c | 204 | if (path_len > 0 && path[path_len-1] == '/' && |
be30387e ET |
205 | iter->strncomp(path, iter->start, path_len) == 0) |
206 | return true; | |
207 | ||
d47f7e1c | 208 | return false; |
be30387e ET |
209 | } |
210 | ||
db22a91b | 211 | GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path) |
be30387e | 212 | { |
4c88198a | 213 | if (iter->end == NULL) |
be30387e | 214 | return false; |
4c88198a ET |
215 | else if (iter->ended) |
216 | return true; | |
be30387e ET |
217 | |
218 | iter->ended = (iter->prefixcomp(path, iter->end) > 0); | |
219 | return iter->ended; | |
220 | } | |
221 | ||
d6713ec6 MS |
222 | /* walker for the index and tree iterator that allows it to walk the sorted |
223 | * pathlist entries alongside sorted iterator entries. | |
be30387e | 224 | */ |
0e0589fc | 225 | static bool iterator_pathlist_next_is(git_iterator *iter, const char *path) |
be30387e ET |
226 | { |
227 | char *p; | |
228 | size_t path_len, p_len, cmp_len, i; | |
229 | int cmp; | |
230 | ||
231 | if (iter->pathlist.length == 0) | |
232 | return true; | |
233 | ||
9eb9e5fa ET |
234 | git_vector_sort(&iter->pathlist); |
235 | ||
be30387e ET |
236 | path_len = strlen(path); |
237 | ||
238 | /* for comparison, drop the trailing slash on the current '/' */ | |
239 | if (path_len && path[path_len-1] == '/') | |
240 | path_len--; | |
241 | ||
242 | for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { | |
243 | p = iter->pathlist.contents[i]; | |
244 | p_len = strlen(p); | |
245 | ||
246 | if (p_len && p[p_len-1] == '/') | |
247 | p_len--; | |
248 | ||
249 | cmp_len = min(path_len, p_len); | |
250 | ||
251 | /* see if the pathlist entry is a prefix of this path */ | |
252 | cmp = iter->strncomp(p, path, cmp_len); | |
253 | ||
254 | /* prefix match - see if there's an exact match, or if we were | |
255 | * given a path that matches the directory | |
256 | */ | |
257 | if (cmp == 0) { | |
258 | /* if this pathlist entry is not suffixed with a '/' then | |
259 | * it matches a path that is a file or a directory. | |
260 | * (eg, pathlist = "foo" and path is "foo" or "foo/" or | |
261 | * "foo/something") | |
262 | */ | |
263 | if (p[cmp_len] == '\0' && | |
264 | (path[cmp_len] == '\0' || path[cmp_len] == '/')) | |
265 | return true; | |
266 | ||
267 | /* if this pathlist entry _is_ suffixed with a '/' then | |
268 | * it matches only paths that are directories. | |
269 | * (eg, pathlist = "foo/" and path is "foo/" or "foo/something") | |
270 | */ | |
271 | if (p[cmp_len] == '/' && path[cmp_len] == '/') | |
272 | return true; | |
be30387e ET |
273 | } |
274 | ||
275 | /* this pathlist entry sorts before the given path, try the next */ | |
0ef0b71c | 276 | else if (cmp < 0) { |
be30387e ET |
277 | iter->pathlist_walk_idx++; |
278 | continue; | |
279 | } | |
280 | ||
281 | /* this pathlist sorts after the given path, no match. */ | |
282 | else if (cmp > 0) { | |
283 | break; | |
284 | } | |
285 | } | |
286 | ||
287 | return false; | |
288 | } | |
289 | ||
0e0589fc | 290 | typedef enum { |
9eb9e5fa | 291 | ITERATOR_PATHLIST_NONE = 0, |
0e0589fc ET |
292 | ITERATOR_PATHLIST_IS_FILE = 1, |
293 | ITERATOR_PATHLIST_IS_DIR = 2, | |
294 | ITERATOR_PATHLIST_IS_PARENT = 3, | |
295 | ITERATOR_PATHLIST_FULL = 4, | |
296 | } iterator_pathlist_search_t; | |
297 | ||
298 | static iterator_pathlist_search_t iterator_pathlist_search( | |
299 | git_iterator *iter, const char *path, size_t path_len) | |
300 | { | |
301 | const char *p; | |
302 | size_t idx; | |
303 | int error; | |
304 | ||
9eb9e5fa ET |
305 | if (iter->pathlist.length == 0) |
306 | return ITERATOR_PATHLIST_FULL; | |
307 | ||
308 | git_vector_sort(&iter->pathlist); | |
309 | ||
0e0589fc ET |
310 | error = git_vector_bsearch2(&idx, &iter->pathlist, |
311 | (git_vector_cmp)iter->strcomp, path); | |
312 | ||
313 | /* the given path was found in the pathlist. since the pathlist only | |
314 | * matches directories when they're suffixed with a '/', analyze the | |
315 | * path string to determine whether it's a directory or not. | |
316 | */ | |
317 | if (error == 0) { | |
318 | if (path_len && path[path_len-1] == '/') | |
319 | return ITERATOR_PATHLIST_IS_DIR; | |
320 | ||
321 | return ITERATOR_PATHLIST_IS_FILE; | |
322 | } | |
323 | ||
324 | /* at this point, the path we're examining may be a directory (though we | |
325 | * don't know that yet, since we're avoiding a stat unless it's necessary) | |
326 | * so walk the pathlist looking for the given path with a '/' after it, | |
327 | */ | |
328 | while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { | |
329 | if (iter->prefixcomp(p, path) != 0) | |
330 | break; | |
331 | ||
332 | /* an exact match would have been matched by the bsearch above */ | |
333 | assert(p[path_len]); | |
334 | ||
335 | /* is this a literal directory entry (eg `foo/`) or a file beneath */ | |
336 | if (p[path_len] == '/') { | |
337 | return (p[path_len+1] == '\0') ? | |
338 | ITERATOR_PATHLIST_IS_DIR : | |
339 | ITERATOR_PATHLIST_IS_PARENT; | |
340 | } | |
341 | ||
342 | if (p[path_len] > '/') | |
343 | break; | |
344 | ||
345 | idx++; | |
346 | } | |
347 | ||
9eb9e5fa | 348 | return ITERATOR_PATHLIST_NONE; |
0e0589fc ET |
349 | } |
350 | ||
be30387e ET |
351 | /* Empty iterator */ |
352 | ||
35877463 | 353 | static int empty_iterator_noop(const git_index_entry **e, git_iterator *i) |
92028ea5 RB |
354 | { |
355 | GIT_UNUSED(i); | |
9eb9e5fa ET |
356 | |
357 | if (e) | |
358 | *e = NULL; | |
359 | ||
cee695ae | 360 | return GIT_ITEROVER; |
7e000ab2 RB |
361 | } |
362 | ||
35877463 ET |
363 | static int empty_iterator_advance_over( |
364 | const git_index_entry **e, | |
365 | git_iterator_status_t *s, | |
366 | git_iterator *i) | |
367 | { | |
35877463 | 368 | *s = GIT_ITERATOR_STATUS_EMPTY; |
9eb9e5fa | 369 | return empty_iterator_noop(e, i); |
35877463 ET |
370 | } |
371 | ||
372 | static int empty_iterator_reset(git_iterator *i) | |
684b35c4 ET |
373 | { |
374 | GIT_UNUSED(i); | |
375 | return 0; | |
376 | } | |
377 | ||
35877463 | 378 | static void empty_iterator_free(git_iterator *i) |
7e000ab2 | 379 | { |
92028ea5 | 380 | GIT_UNUSED(i); |
7e000ab2 RB |
381 | } |
382 | ||
f616a36b RB |
383 | typedef struct { |
384 | git_iterator base; | |
385 | git_iterator_callbacks cb; | |
386 | } empty_iterator; | |
387 | ||
169dc616 | 388 | int git_iterator_for_nothing( |
35877463 | 389 | git_iterator **out, |
ed1c6446 | 390 | git_iterator_options *options) |
7e000ab2 | 391 | { |
35877463 ET |
392 | empty_iterator *iter; |
393 | ||
394 | static git_iterator_callbacks callbacks = { | |
395 | empty_iterator_noop, | |
396 | empty_iterator_noop, | |
397 | empty_iterator_noop, | |
398 | empty_iterator_advance_over, | |
399 | empty_iterator_reset, | |
35877463 ET |
400 | empty_iterator_free |
401 | }; | |
7e000ab2 | 402 | |
35877463 | 403 | *out = NULL; |
169dc616 | 404 | |
35877463 | 405 | iter = git__calloc(1, sizeof(empty_iterator)); |
ac3d33df | 406 | GIT_ERROR_CHECK_ALLOC(iter); |
169dc616 | 407 | |
0c9c969a | 408 | iter->base.type = GIT_ITERATOR_EMPTY; |
35877463 ET |
409 | iter->base.cb = &callbacks; |
410 | iter->base.flags = options->flags; | |
7e000ab2 | 411 | |
35877463 | 412 | *out = &iter->base; |
7e000ab2 RB |
413 | return 0; |
414 | } | |
415 | ||
be30387e | 416 | /* Tree iterator */ |
41a82592 | 417 | |
be30387e ET |
418 | typedef struct { |
419 | git_tree_entry *tree_entry; | |
420 | const char *parent_path; | |
421 | } tree_iterator_entry; | |
e40f1c2d | 422 | |
be30387e ET |
423 | typedef struct { |
424 | git_tree *tree; | |
e40f1c2d | 425 | |
97054833 ET |
426 | /* path to this particular frame (folder) */ |
427 | git_buf path; | |
428 | ||
be30387e ET |
429 | /* a sorted list of the entries for this frame (folder), these are |
430 | * actually pointers to the iterator's entry pool. | |
431 | */ | |
432 | git_vector entries; | |
433 | tree_iterator_entry *current; | |
e40f1c2d | 434 | |
be30387e | 435 | size_t next_idx; |
e40f1c2d | 436 | |
97054833 ET |
437 | /* on case insensitive iterations, we also have an array of other |
438 | * paths that were case insensitively equal to this one, and their | |
439 | * tree objects. we have coalesced the tree entries into this frame. | |
440 | * a child `tree_iterator_entry` will contain a pointer to its actual | |
441 | * parent path. | |
be30387e | 442 | */ |
97054833 | 443 | git_vector similar_trees; |
be30387e ET |
444 | git_array_t(git_buf) similar_paths; |
445 | } tree_iterator_frame; | |
b6c93aef RB |
446 | |
447 | typedef struct { | |
0534641d | 448 | git_iterator base; |
be30387e ET |
449 | git_tree *root; |
450 | git_array_t(tree_iterator_frame) frames; | |
451 | ||
b6c93aef | 452 | git_index_entry entry; |
be30387e ET |
453 | git_buf entry_path; |
454 | ||
455 | /* a pool of entries to reduce the number of allocations */ | |
456 | git_pool entry_pool; | |
0534641d | 457 | } tree_iterator; |
b6c93aef | 458 | |
be30387e ET |
459 | GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame( |
460 | tree_iterator *iter) | |
41a82592 | 461 | { |
be30387e ET |
462 | return iter->frames.size > 1 ? |
463 | &iter->frames.ptr[iter->frames.size-2] : NULL; | |
41a82592 RB |
464 | } |
465 | ||
be30387e ET |
466 | GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame( |
467 | tree_iterator *iter) | |
41a82592 | 468 | { |
be30387e | 469 | return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; |
91e7d263 | 470 | } |
41a82592 | 471 | |
be30387e ET |
472 | GIT_INLINE(int) tree_entry_cmp( |
473 | const git_tree_entry *a, const git_tree_entry *b, bool icase) | |
91e7d263 | 474 | { |
0c468633 RB |
475 | return git_path_cmp( |
476 | a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE, | |
477 | b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE, | |
be30387e | 478 | icase ? git__strncasecmp : git__strncmp); |
41a82592 RB |
479 | } |
480 | ||
be30387e ET |
481 | GIT_INLINE(int) tree_iterator_entry_cmp_icase( |
482 | const void *ptr_a, const void *ptr_b) | |
0c468633 | 483 | { |
be30387e ET |
484 | const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; |
485 | const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; | |
0c468633 | 486 | |
be30387e | 487 | return tree_entry_cmp(a->tree_entry, b->tree_entry, true); |
0c468633 RB |
488 | } |
489 | ||
be30387e | 490 | static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b) |
cee695ae | 491 | { |
be30387e ET |
492 | const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; |
493 | const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; | |
cee695ae | 494 | |
be30387e | 495 | int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true); |
cee695ae | 496 | |
be30387e ET |
497 | /* stabilize the sort order for filenames that are (case insensitively) |
498 | * the same by examining the parent path (case sensitively) before | |
499 | * falling back to a case sensitive sort of the filename. | |
500 | */ | |
501 | if (!c && a->parent_path != b->parent_path) | |
502 | c = git__strcmp(a->parent_path, b->parent_path); | |
503 | ||
504 | if (!c) | |
505 | c = tree_entry_cmp(a->tree_entry, b->tree_entry, false); | |
cee695ae | 506 | |
be30387e | 507 | return c; |
cee695ae RB |
508 | } |
509 | ||
be30387e ET |
510 | static int tree_iterator_compute_path( |
511 | git_buf *out, | |
512 | tree_iterator_entry *entry) | |
b6c93aef | 513 | { |
be30387e | 514 | git_buf_clear(out); |
0d0fa7c3 | 515 | |
be30387e ET |
516 | if (entry->parent_path) |
517 | git_buf_joinpath(out, entry->parent_path, entry->tree_entry->filename); | |
518 | else | |
519 | git_buf_puts(out, entry->tree_entry->filename); | |
61c7b61e | 520 | |
be30387e ET |
521 | if (git_tree_entry__is_tree(entry->tree_entry)) |
522 | git_buf_putc(out, '/'); | |
cee695ae | 523 | |
be30387e ET |
524 | if (git_buf_oom(out)) |
525 | return -1; | |
b6c93aef | 526 | |
0c468633 | 527 | return 0; |
b6c93aef RB |
528 | } |
529 | ||
be30387e ET |
530 | static int tree_iterator_frame_init( |
531 | tree_iterator *iter, | |
532 | git_tree *tree, | |
533 | tree_iterator_entry *frame_entry) | |
25423d03 | 534 | { |
be30387e ET |
535 | tree_iterator_frame *new_frame = NULL; |
536 | tree_iterator_entry *new_entry; | |
537 | git_tree *dup = NULL; | |
538 | git_tree_entry *tree_entry; | |
539 | git_vector_cmp cmp; | |
540 | size_t i; | |
e40f1c2d | 541 | int error = 0; |
0c468633 | 542 | |
be30387e | 543 | new_frame = git_array_alloc(iter->frames); |
ac3d33df | 544 | GIT_ERROR_CHECK_ALLOC(new_frame); |
392702ee | 545 | |
be30387e ET |
546 | if ((error = git_tree_dup(&dup, tree)) < 0) |
547 | goto done; | |
e40f1c2d | 548 | |
be30387e ET |
549 | memset(new_frame, 0x0, sizeof(tree_iterator_frame)); |
550 | new_frame->tree = dup; | |
e40f1c2d | 551 | |
be30387e | 552 | if (frame_entry && |
0c9c969a | 553 | (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0) |
be30387e | 554 | goto done; |
0c468633 | 555 | |
be30387e ET |
556 | cmp = iterator__ignore_case(&iter->base) ? |
557 | tree_iterator_entry_sort_icase : NULL; | |
169dc616 | 558 | |
0c9c969a UG |
559 | if ((error = git_vector_init(&new_frame->entries, |
560 | dup->entries.size, cmp)) < 0) | |
be30387e | 561 | goto done; |
0c468633 | 562 | |
be30387e | 563 | git_array_foreach(dup->entries, i, tree_entry) { |
0c9c969a UG |
564 | if ((new_entry = git_pool_malloc(&iter->entry_pool, 1)) == NULL) { |
565 | git_error_set_oom(); | |
566 | error = -1; | |
567 | goto done; | |
568 | } | |
0c468633 | 569 | |
be30387e ET |
570 | new_entry->tree_entry = tree_entry; |
571 | new_entry->parent_path = new_frame->path.ptr; | |
41a82592 | 572 | |
be30387e ET |
573 | if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0) |
574 | goto done; | |
41a82592 RB |
575 | } |
576 | ||
be30387e ET |
577 | git_vector_set_sorted(&new_frame->entries, |
578 | !iterator__ignore_case(&iter->base)); | |
169dc616 | 579 | |
be30387e ET |
580 | done: |
581 | if (error < 0) { | |
582 | git_tree_free(dup); | |
583 | git_array_pop(iter->frames); | |
584 | } | |
25423d03 | 585 | |
be30387e ET |
586 | return error; |
587 | } | |
25423d03 | 588 | |
be30387e ET |
589 | GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry( |
590 | tree_iterator_frame *frame) | |
591 | { | |
592 | return frame->current; | |
e40f1c2d | 593 | } |
25423d03 | 594 | |
be30387e ET |
595 | GIT_INLINE(int) tree_iterator_frame_push_neighbors( |
596 | tree_iterator *iter, | |
597 | tree_iterator_frame *parent_frame, | |
598 | tree_iterator_frame *frame, | |
599 | const char *filename) | |
0534641d | 600 | { |
be30387e ET |
601 | tree_iterator_entry *entry, *new_entry; |
602 | git_tree *tree = NULL; | |
603 | git_tree_entry *tree_entry; | |
604 | git_buf *path; | |
605 | size_t new_size, i; | |
606 | int error = 0; | |
b6c93aef | 607 | |
be30387e ET |
608 | while (parent_frame->next_idx < parent_frame->entries.length) { |
609 | entry = parent_frame->entries.contents[parent_frame->next_idx]; | |
4fea9cff | 610 | |
be30387e ET |
611 | if (strcasecmp(filename, entry->tree_entry->filename) != 0) |
612 | break; | |
41a82592 | 613 | |
be30387e ET |
614 | if ((error = git_tree_lookup(&tree, |
615 | iter->base.repo, entry->tree_entry->oid)) < 0) | |
616 | break; | |
0c468633 | 617 | |
97054833 ET |
618 | if (git_vector_insert(&parent_frame->similar_trees, tree) < 0) |
619 | break; | |
620 | ||
be30387e | 621 | path = git_array_alloc(parent_frame->similar_paths); |
ac3d33df | 622 | GIT_ERROR_CHECK_ALLOC(path); |
41a82592 | 623 | |
be30387e | 624 | memset(path, 0, sizeof(git_buf)); |
b6c93aef | 625 | |
be30387e ET |
626 | if ((error = tree_iterator_compute_path(path, entry)) < 0) |
627 | break; | |
41a82592 | 628 | |
ac3d33df | 629 | GIT_ERROR_CHECK_ALLOC_ADD(&new_size, |
be30387e ET |
630 | frame->entries.length, tree->entries.size); |
631 | git_vector_size_hint(&frame->entries, new_size); | |
41a82592 | 632 | |
be30387e ET |
633 | git_array_foreach(tree->entries, i, tree_entry) { |
634 | new_entry = git_pool_malloc(&iter->entry_pool, 1); | |
ac3d33df | 635 | GIT_ERROR_CHECK_ALLOC(new_entry); |
be30387e ET |
636 | |
637 | new_entry->tree_entry = tree_entry; | |
638 | new_entry->parent_path = path->ptr; | |
0c468633 | 639 | |
be30387e ET |
640 | if ((error = git_vector_insert(&frame->entries, new_entry)) < 0) |
641 | break; | |
642 | } | |
643 | ||
644 | if (error) | |
645 | break; | |
4fea9cff | 646 | |
be30387e | 647 | parent_frame->next_idx++; |
0c468633 | 648 | } |
be30387e ET |
649 | |
650 | return error; | |
0c468633 RB |
651 | } |
652 | ||
be30387e ET |
653 | GIT_INLINE(int) tree_iterator_frame_push( |
654 | tree_iterator *iter, tree_iterator_entry *entry) | |
e40f1c2d | 655 | { |
be30387e ET |
656 | tree_iterator_frame *parent_frame, *frame; |
657 | git_tree *tree = NULL; | |
658 | int error; | |
0534641d | 659 | |
be30387e ET |
660 | if ((error = git_tree_lookup(&tree, |
661 | iter->base.repo, entry->tree_entry->oid)) < 0 || | |
662 | (error = tree_iterator_frame_init(iter, tree, entry)) < 0) | |
663 | goto done; | |
e40f1c2d | 664 | |
be30387e ET |
665 | parent_frame = tree_iterator_parent_frame(iter); |
666 | frame = tree_iterator_current_frame(iter); | |
9bea03ce | 667 | |
be30387e ET |
668 | /* if we're case insensitive, then we may have another directory that |
669 | * is (case insensitively) equal to this one. coalesce those children | |
670 | * into this tree. | |
671 | */ | |
672 | if (iterator__ignore_case(&iter->base)) | |
673 | error = tree_iterator_frame_push_neighbors(iter, | |
674 | parent_frame, frame, entry->tree_entry->filename); | |
e40f1c2d | 675 | |
be30387e ET |
676 | done: |
677 | git_tree_free(tree); | |
678 | return error; | |
679 | } | |
e40f1c2d | 680 | |
be30387e ET |
681 | static void tree_iterator_frame_pop(tree_iterator *iter) |
682 | { | |
683 | tree_iterator_frame *frame; | |
f5c874a4 | 684 | git_buf *buf = NULL; |
97054833 ET |
685 | git_tree *tree; |
686 | size_t i; | |
61c7b61e | 687 | |
be30387e | 688 | assert(iter->frames.size); |
cee695ae | 689 | |
be30387e | 690 | frame = git_array_pop(iter->frames); |
cee695ae | 691 | |
be30387e ET |
692 | git_vector_free(&frame->entries); |
693 | git_tree_free(frame->tree); | |
f5c874a4 CMN |
694 | |
695 | do { | |
696 | buf = git_array_pop(frame->similar_paths); | |
ac3d33df | 697 | git_buf_dispose(buf); |
f5c874a4 CMN |
698 | } while (buf != NULL); |
699 | ||
700 | git_array_clear(frame->similar_paths); | |
97054833 ET |
701 | |
702 | git_vector_foreach(&frame->similar_trees, i, tree) | |
703 | git_tree_free(tree); | |
704 | ||
705 | git_vector_free(&frame->similar_trees); | |
706 | ||
ac3d33df | 707 | git_buf_dispose(&frame->path); |
cee695ae RB |
708 | } |
709 | ||
be30387e ET |
710 | static int tree_iterator_current( |
711 | const git_index_entry **out, git_iterator *i) | |
cee695ae | 712 | { |
be30387e | 713 | tree_iterator *iter = (tree_iterator *)i; |
cee695ae | 714 | |
be30387e ET |
715 | if (!iterator__has_been_accessed(i)) |
716 | return iter->base.cb->advance(out, i); | |
cee695ae | 717 | |
be30387e ET |
718 | if (!iter->frames.size) { |
719 | *out = NULL; | |
cee695ae | 720 | return GIT_ITEROVER; |
be30387e | 721 | } |
cee695ae | 722 | |
be30387e | 723 | *out = &iter->entry; |
0d0fa7c3 | 724 | return 0; |
b6c93aef RB |
725 | } |
726 | ||
be30387e ET |
727 | static void tree_iterator_set_current( |
728 | tree_iterator *iter, | |
729 | tree_iterator_frame *frame, | |
730 | tree_iterator_entry *entry) | |
a1859e21 | 731 | { |
be30387e | 732 | git_tree_entry *tree_entry = entry->tree_entry; |
a1859e21 | 733 | |
be30387e | 734 | frame->current = entry; |
a1859e21 | 735 | |
be30387e ET |
736 | memset(&iter->entry, 0x0, sizeof(git_index_entry)); |
737 | ||
738 | iter->entry.mode = tree_entry->attr; | |
739 | iter->entry.path = iter->entry_path.ptr; | |
740 | git_oid_cpy(&iter->entry.id, tree_entry->oid); | |
a1859e21 ET |
741 | } |
742 | ||
be30387e | 743 | static int tree_iterator_advance(const git_index_entry **out, git_iterator *i) |
a1859e21 | 744 | { |
be30387e ET |
745 | tree_iterator *iter = (tree_iterator *)i; |
746 | int error = 0; | |
a1859e21 | 747 | |
be30387e | 748 | iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; |
a1859e21 | 749 | |
be30387e ET |
750 | /* examine tree entries until we find the next one to return */ |
751 | while (true) { | |
752 | tree_iterator_entry *prev_entry, *entry; | |
753 | tree_iterator_frame *frame; | |
754 | bool is_tree; | |
755 | ||
756 | if ((frame = tree_iterator_current_frame(iter)) == NULL) { | |
757 | error = GIT_ITEROVER; | |
758 | break; | |
759 | } | |
a1859e21 | 760 | |
be30387e ET |
761 | /* no more entries in this frame. pop the frame out */ |
762 | if (frame->next_idx == frame->entries.length) { | |
763 | tree_iterator_frame_pop(iter); | |
764 | continue; | |
765 | } | |
a1859e21 | 766 | |
be30387e ET |
767 | /* we may have coalesced the contents of case-insensitively same-named |
768 | * directories, so do the sort now. | |
769 | */ | |
770 | if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries)) | |
771 | git_vector_sort(&frame->entries); | |
a1859e21 | 772 | |
be30387e ET |
773 | /* we have more entries in the current frame, that's our next entry */ |
774 | prev_entry = tree_iterator_current_entry(frame); | |
775 | entry = frame->entries.contents[frame->next_idx]; | |
776 | frame->next_idx++; | |
a1859e21 | 777 | |
be30387e ET |
778 | /* we can have collisions when iterating case insensitively. (eg, |
779 | * 'A/a' and 'a/A'). squash this one if it's already been seen. | |
780 | */ | |
781 | if (iterator__ignore_case(&iter->base) && | |
782 | prev_entry && | |
783 | tree_iterator_entry_cmp_icase(prev_entry, entry) == 0) | |
784 | continue; | |
a1859e21 | 785 | |
be30387e ET |
786 | if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0) |
787 | break; | |
a1859e21 | 788 | |
be30387e | 789 | /* if this path is before our start, advance over this entry */ |
d47f7e1c | 790 | if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false)) |
be30387e | 791 | continue; |
1af84271 | 792 | |
be30387e | 793 | /* if this path is after our end, stop */ |
db22a91b | 794 | if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) { |
be30387e ET |
795 | error = GIT_ITEROVER; |
796 | break; | |
797 | } | |
1af84271 | 798 | |
be30387e | 799 | /* if we have a list of paths we're interested in, examine it */ |
0e0589fc | 800 | if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr)) |
be30387e | 801 | continue; |
1af84271 | 802 | |
be30387e | 803 | is_tree = git_tree_entry__is_tree(entry->tree_entry); |
1af84271 | 804 | |
be30387e ET |
805 | /* if we are *not* including trees then advance over this entry */ |
806 | if (is_tree && !iterator__include_trees(iter)) { | |
1af84271 | 807 | |
be30387e ET |
808 | /* if we've found a tree (and are not returning it to the caller) |
809 | * and we are autoexpanding, then we want to return the first | |
810 | * child. push the new directory and advance. | |
811 | */ | |
812 | if (iterator__do_autoexpand(iter)) { | |
813 | if ((error = tree_iterator_frame_push(iter, entry)) < 0) | |
814 | break; | |
1af84271 | 815 | } |
be30387e ET |
816 | |
817 | continue; | |
1af84271 | 818 | } |
be30387e ET |
819 | |
820 | tree_iterator_set_current(iter, frame, entry); | |
821 | ||
822 | /* if we are autoexpanding, then push this as a new frame, so that | |
823 | * the next call to `advance` will dive into this directory. | |
824 | */ | |
825 | if (is_tree && iterator__do_autoexpand(iter)) | |
826 | error = tree_iterator_frame_push(iter, entry); | |
827 | ||
828 | break; | |
829 | } | |
1af84271 ET |
830 | |
831 | if (out) | |
be30387e | 832 | *out = (error == 0) ? &iter->entry : NULL; |
1af84271 ET |
833 | |
834 | return error; | |
835 | } | |
836 | ||
be30387e ET |
837 | static int tree_iterator_advance_into( |
838 | const git_index_entry **out, git_iterator *i) | |
9bea03ce | 839 | { |
be30387e ET |
840 | tree_iterator *iter = (tree_iterator *)i; |
841 | tree_iterator_frame *frame; | |
842 | tree_iterator_entry *prev_entry; | |
843 | int error; | |
9bea03ce | 844 | |
be30387e ET |
845 | if (out) |
846 | *out = NULL; | |
9bea03ce | 847 | |
be30387e ET |
848 | if ((frame = tree_iterator_current_frame(iter)) == NULL) |
849 | return GIT_ITEROVER; | |
850 | ||
851 | /* get the last seen entry */ | |
852 | prev_entry = tree_iterator_current_entry(frame); | |
853 | ||
854 | /* it's legal to call advance_into when auto-expand is on. in this case, | |
855 | * we will have pushed a new (empty) frame on to the stack for this | |
856 | * new directory. since it's empty, its current_entry should be null. | |
857 | */ | |
858 | assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); | |
859 | ||
860 | if (prev_entry) { | |
861 | if (!git_tree_entry__is_tree(prev_entry->tree_entry)) | |
862 | return 0; | |
9bea03ce | 863 | |
be30387e ET |
864 | if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0) |
865 | return error; | |
866 | } | |
867 | ||
868 | /* we've advanced into the directory in question, let advance | |
869 | * find the first entry | |
870 | */ | |
871 | return tree_iterator_advance(out, i); | |
9bea03ce RB |
872 | } |
873 | ||
247e3b43 ET |
874 | static int tree_iterator_advance_over( |
875 | const git_index_entry **out, | |
876 | git_iterator_status_t *status, | |
877 | git_iterator *i) | |
878 | { | |
879 | *status = GIT_ITERATOR_STATUS_NORMAL; | |
880 | return git_iterator_advance(out, i); | |
881 | } | |
882 | ||
be30387e | 883 | static void tree_iterator_clear(tree_iterator *iter) |
b6c93aef | 884 | { |
be30387e ET |
885 | while (iter->frames.size) |
886 | tree_iterator_frame_pop(iter); | |
b6c93aef | 887 | |
be30387e | 888 | git_array_clear(iter->frames); |
da337c80 | 889 | |
be30387e ET |
890 | git_pool_clear(&iter->entry_pool); |
891 | git_buf_clear(&iter->entry_path); | |
b6c93aef | 892 | |
be30387e | 893 | iterator_clear(&iter->base); |
b6c93aef RB |
894 | } |
895 | ||
be30387e | 896 | static int tree_iterator_init(tree_iterator *iter) |
b6c93aef | 897 | { |
be30387e ET |
898 | int error; |
899 | ||
900 | git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry)); | |
91e7d263 | 901 | |
be30387e ET |
902 | if ((error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0) |
903 | return error; | |
684b35c4 | 904 | |
be30387e ET |
905 | iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; |
906 | ||
907 | return 0; | |
684b35c4 | 908 | } |
91e7d263 | 909 | |
be30387e | 910 | static int tree_iterator_reset(git_iterator *i) |
684b35c4 | 911 | { |
be30387e | 912 | tree_iterator *iter = (tree_iterator *)i; |
91e7d263 | 913 | |
be30387e ET |
914 | tree_iterator_clear(iter); |
915 | return tree_iterator_init(iter); | |
b6c93aef RB |
916 | } |
917 | ||
be30387e | 918 | static void tree_iterator_free(git_iterator *i) |
61c7b61e | 919 | { |
be30387e | 920 | tree_iterator *iter = (tree_iterator *)i; |
61c7b61e | 921 | |
be30387e | 922 | tree_iterator_clear(iter); |
61c7b61e | 923 | |
be30387e | 924 | git_tree_free(iter->root); |
ac3d33df | 925 | git_buf_dispose(&iter->entry_path); |
61c7b61e RB |
926 | } |
927 | ||
169dc616 | 928 | int git_iterator_for_tree( |
be30387e | 929 | git_iterator **out, |
41a82592 | 930 | git_tree *tree, |
ed1c6446 | 931 | git_iterator_options *options) |
b6c93aef | 932 | { |
be30387e | 933 | tree_iterator *iter; |
b6c93aef | 934 | int error; |
be30387e ET |
935 | |
936 | static git_iterator_callbacks callbacks = { | |
937 | tree_iterator_current, | |
938 | tree_iterator_advance, | |
939 | tree_iterator_advance_into, | |
247e3b43 | 940 | tree_iterator_advance_over, |
be30387e | 941 | tree_iterator_reset, |
be30387e ET |
942 | tree_iterator_free |
943 | }; | |
944 | ||
945 | *out = NULL; | |
7e000ab2 RB |
946 | |
947 | if (tree == NULL) | |
be30387e | 948 | return git_iterator_for_nothing(out, options); |
7e000ab2 | 949 | |
be30387e | 950 | iter = git__calloc(1, sizeof(tree_iterator)); |
ac3d33df | 951 | GIT_ERROR_CHECK_ALLOC(iter); |
91e7d263 | 952 | |
0c9c969a | 953 | iter->base.type = GIT_ITERATOR_TREE; |
be30387e | 954 | iter->base.cb = &callbacks; |
71f85226 | 955 | |
be30387e | 956 | if ((error = iterator_init_common(&iter->base, |
0ef0b71c | 957 | git_tree_owner(tree), NULL, options)) < 0 || |
be30387e ET |
958 | (error = git_tree_dup(&iter->root, tree)) < 0 || |
959 | (error = tree_iterator_init(iter)) < 0) | |
960 | goto on_error; | |
134d8c91 | 961 | |
be30387e ET |
962 | *out = &iter->base; |
963 | return 0; | |
e40f1c2d | 964 | |
be30387e ET |
965 | on_error: |
966 | git_iterator_free(&iter->base); | |
967 | return error; | |
968 | } | |
1e5e02b4 | 969 | |
be30387e ET |
970 | int git_iterator_current_tree_entry( |
971 | const git_tree_entry **tree_entry, git_iterator *i) | |
972 | { | |
973 | tree_iterator *iter; | |
974 | tree_iterator_frame *frame; | |
975 | tree_iterator_entry *entry; | |
134d8c91 | 976 | |
0c9c969a | 977 | assert(i->type == GIT_ITERATOR_TREE); |
be30387e ET |
978 | |
979 | iter = (tree_iterator *)i; | |
980 | ||
981 | frame = tree_iterator_current_frame(iter); | |
982 | entry = tree_iterator_current_entry(frame); | |
983 | ||
984 | *tree_entry = entry->tree_entry; | |
134d8c91 | 985 | return 0; |
be30387e | 986 | } |
41a82592 | 987 | |
be30387e ET |
988 | int git_iterator_current_parent_tree( |
989 | const git_tree **parent_tree, git_iterator *i, size_t depth) | |
990 | { | |
991 | tree_iterator *iter; | |
992 | tree_iterator_frame *frame; | |
993 | ||
0c9c969a | 994 | assert(i->type == GIT_ITERATOR_TREE); |
be30387e ET |
995 | |
996 | iter = (tree_iterator *)i; | |
997 | ||
998 | assert(depth < iter->frames.size); | |
999 | frame = &iter->frames.ptr[iter->frames.size-depth-1]; | |
1000 | ||
1001 | *parent_tree = frame->tree; | |
1002 | return 0; | |
b6c93aef RB |
1003 | } |
1004 | ||
0e0589fc ET |
1005 | /* Filesystem iterator */ |
1006 | ||
1007 | typedef struct { | |
1008 | struct stat st; | |
1009 | size_t path_len; | |
1010 | iterator_pathlist_search_t match; | |
ac3d33df | 1011 | git_oid id; |
0e0589fc ET |
1012 | char path[GIT_FLEX_ARRAY]; |
1013 | } filesystem_iterator_entry; | |
1014 | ||
1015 | typedef struct { | |
1016 | git_vector entries; | |
1017 | git_pool entry_pool; | |
1018 | size_t next_idx; | |
be30387e | 1019 | |
0e0589fc ET |
1020 | size_t path_len; |
1021 | int is_ignored; | |
1022 | } filesystem_iterator_frame; | |
b6c93aef RB |
1023 | |
1024 | typedef struct { | |
0534641d | 1025 | git_iterator base; |
0e0589fc ET |
1026 | char *root; |
1027 | size_t root_len; | |
1028 | ||
1029 | unsigned int dirload_flags; | |
1030 | ||
1031 | git_tree *tree; | |
b6c93aef | 1032 | git_index *index; |
0e0589fc | 1033 | git_vector index_snapshot; |
b6c93aef | 1034 | |
0e0589fc ET |
1035 | git_array_t(filesystem_iterator_frame) frames; |
1036 | git_ignores ignores; | |
9bea03ce | 1037 | |
0e0589fc ET |
1038 | /* info about the current entry */ |
1039 | git_index_entry entry; | |
1040 | git_buf current_path; | |
1041 | int current_is_ignored; | |
9bea03ce | 1042 | |
0e0589fc ET |
1043 | /* temporary buffer for advance_over */ |
1044 | git_buf tmp_buf; | |
1045 | } filesystem_iterator; | |
1046 | ||
1047 | ||
1048 | GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame( | |
1049 | filesystem_iterator *iter) | |
1050 | { | |
1051 | return iter->frames.size > 1 ? | |
1052 | &iter->frames.ptr[iter->frames.size-2] : NULL; | |
9bea03ce RB |
1053 | } |
1054 | ||
0e0589fc ET |
1055 | GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame( |
1056 | filesystem_iterator *iter) | |
9bea03ce | 1057 | { |
0e0589fc ET |
1058 | return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; |
1059 | } | |
9bea03ce | 1060 | |
0e0589fc ET |
1061 | GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry( |
1062 | filesystem_iterator_frame *frame) | |
1063 | { | |
1064 | return frame->next_idx == 0 ? | |
1065 | NULL : frame->entries.contents[frame->next_idx-1]; | |
1066 | } | |
ef206124 | 1067 | |
0e0589fc ET |
1068 | static int filesystem_iterator_entry_cmp(const void *_a, const void *_b) |
1069 | { | |
1070 | const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; | |
1071 | const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; | |
4a0dbeb0 | 1072 | |
0e0589fc ET |
1073 | return git__strcmp(a->path, b->path); |
1074 | } | |
ef206124 | 1075 | |
0e0589fc ET |
1076 | static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b) |
1077 | { | |
1078 | const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; | |
1079 | const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; | |
9bea03ce | 1080 | |
0e0589fc | 1081 | return git__strcasecmp(a->path, b->path); |
9bea03ce RB |
1082 | } |
1083 | ||
0e0589fc ET |
1084 | #define FILESYSTEM_MAX_DEPTH 100 |
1085 | ||
1086 | /** | |
1087 | * Figure out if an entry is a submodule. | |
1088 | * | |
1089 | * We consider it a submodule if the path is listed as a submodule in | |
1090 | * either the tree or the index. | |
1091 | */ | |
9eb9e5fa | 1092 | static int filesystem_iterator_is_submodule( |
0e0589fc | 1093 | bool *out, filesystem_iterator *iter, const char *path, size_t path_len) |
9bea03ce | 1094 | { |
0e0589fc ET |
1095 | bool is_submodule = false; |
1096 | int error; | |
9bea03ce | 1097 | |
0e0589fc | 1098 | *out = false; |
9bea03ce | 1099 | |
0e0589fc ET |
1100 | /* first see if this path is a submodule in HEAD */ |
1101 | if (iter->tree) { | |
1102 | git_tree_entry *entry; | |
9bea03ce | 1103 | |
0e0589fc | 1104 | error = git_tree_entry_bypath(&entry, iter->tree, path); |
9bea03ce | 1105 | |
0e0589fc ET |
1106 | if (error < 0 && error != GIT_ENOTFOUND) |
1107 | return error; | |
9bea03ce | 1108 | |
0e0589fc ET |
1109 | if (!error) { |
1110 | is_submodule = (entry->attr == GIT_FILEMODE_COMMIT); | |
1111 | git_tree_entry_free(entry); | |
1112 | } | |
1113 | } | |
9bea03ce | 1114 | |
82a1aab6 | 1115 | if (!is_submodule && iter->base.index) { |
0e0589fc | 1116 | size_t pos; |
9bea03ce | 1117 | |
0e0589fc ET |
1118 | error = git_index_snapshot_find(&pos, |
1119 | &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0); | |
9bea03ce | 1120 | |
0e0589fc ET |
1121 | if (error < 0 && error != GIT_ENOTFOUND) |
1122 | return error; | |
9bea03ce | 1123 | |
0e0589fc ET |
1124 | if (!error) { |
1125 | git_index_entry *e = git_vector_get(&iter->index_snapshot, pos); | |
1126 | is_submodule = (e->mode == GIT_FILEMODE_COMMIT); | |
1127 | } | |
1128 | } | |
9bea03ce | 1129 | |
0e0589fc | 1130 | *out = is_submodule; |
9bea03ce RB |
1131 | return 0; |
1132 | } | |
1133 | ||
0e0589fc ET |
1134 | static void filesystem_iterator_frame_push_ignores( |
1135 | filesystem_iterator *iter, | |
1136 | filesystem_iterator_entry *frame_entry, | |
1137 | filesystem_iterator_frame *new_frame) | |
b6c93aef | 1138 | { |
0e0589fc ET |
1139 | filesystem_iterator_frame *previous_frame; |
1140 | const char *path = frame_entry ? frame_entry->path : ""; | |
41a82592 | 1141 | |
0e0589fc ET |
1142 | if (!iterator__honor_ignores(&iter->base)) |
1143 | return; | |
1144 | ||
1145 | if (git_ignore__lookup(&new_frame->is_ignored, | |
1146 | &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) { | |
ac3d33df | 1147 | git_error_clear(); |
0e0589fc | 1148 | new_frame->is_ignored = GIT_IGNORE_NOTFOUND; |
9bea03ce RB |
1149 | } |
1150 | ||
0e0589fc ET |
1151 | /* if this is not the top level directory... */ |
1152 | if (frame_entry) { | |
1153 | const char *relative_path; | |
41a82592 | 1154 | |
0e0589fc | 1155 | previous_frame = filesystem_iterator_parent_frame(iter); |
cee695ae | 1156 | |
0e0589fc ET |
1157 | /* push new ignores for files in this directory */ |
1158 | relative_path = frame_entry->path + previous_frame->path_len; | |
1159 | ||
1160 | /* inherit ignored from parent if no rule specified */ | |
1161 | if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND) | |
1162 | new_frame->is_ignored = previous_frame->is_ignored; | |
1163 | ||
1164 | git_ignore__push_dir(&iter->ignores, relative_path); | |
1165 | } | |
b6c93aef RB |
1166 | } |
1167 | ||
0e0589fc ET |
1168 | static void filesystem_iterator_frame_pop_ignores( |
1169 | filesystem_iterator *iter) | |
b6c93aef | 1170 | { |
0e0589fc ET |
1171 | if (iterator__honor_ignores(&iter->base)) |
1172 | git_ignore__pop_dir(&iter->ignores); | |
b6c93aef RB |
1173 | } |
1174 | ||
0e0589fc ET |
1175 | GIT_INLINE(bool) filesystem_iterator_examine_path( |
1176 | bool *is_dir_out, | |
1177 | iterator_pathlist_search_t *match_out, | |
1178 | filesystem_iterator *iter, | |
1179 | filesystem_iterator_entry *frame_entry, | |
1180 | const char *path, | |
1181 | size_t path_len) | |
b2414661 | 1182 | { |
0e0589fc ET |
1183 | bool is_dir = 0; |
1184 | iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL; | |
9bea03ce | 1185 | |
0e0589fc | 1186 | *is_dir_out = false; |
9eb9e5fa | 1187 | *match_out = ITERATOR_PATHLIST_NONE; |
cee695ae | 1188 | |
0e0589fc ET |
1189 | if (iter->base.start_len) { |
1190 | int cmp = iter->base.strncomp(path, iter->base.start, path_len); | |
9bea03ce | 1191 | |
0e0589fc ET |
1192 | /* we haven't stat'ed `path` yet, so we don't yet know if it's a |
1193 | * directory or not. special case if the current path may be a | |
1194 | * directory that matches the start prefix. | |
1195 | */ | |
1196 | if (cmp == 0) { | |
1197 | if (iter->base.start[path_len] == '/') | |
1198 | is_dir = true; | |
b2414661 | 1199 | |
0e0589fc ET |
1200 | else if (iter->base.start[path_len] != '\0') |
1201 | cmp = -1; | |
b2414661 ET |
1202 | } |
1203 | ||
0e0589fc ET |
1204 | if (cmp < 0) |
1205 | return false; | |
b2414661 | 1206 | } |
9bea03ce | 1207 | |
0e0589fc ET |
1208 | if (iter->base.end_len) { |
1209 | int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len); | |
41a82592 | 1210 | |
0e0589fc ET |
1211 | if (cmp > 0) |
1212 | return false; | |
9bea03ce | 1213 | } |
b2414661 | 1214 | |
0e0589fc ET |
1215 | /* if we have a pathlist that we're limiting to, examine this path now |
1216 | * to avoid a `stat` if we're not interested in the path. | |
ef206124 | 1217 | */ |
0e0589fc ET |
1218 | if (iter->base.pathlist.length) { |
1219 | /* if our parent was explicitly included, so too are we */ | |
1220 | if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT) | |
1221 | match = ITERATOR_PATHLIST_FULL; | |
1222 | else | |
1223 | match = iterator_pathlist_search(&iter->base, path, path_len); | |
9bea03ce | 1224 | |
9eb9e5fa | 1225 | if (match == ITERATOR_PATHLIST_NONE) |
0e0589fc | 1226 | return false; |
9bea03ce | 1227 | |
0e0589fc ET |
1228 | /* Ensure that the pathlist entry lines up with what we expected */ |
1229 | if (match == ITERATOR_PATHLIST_IS_DIR || | |
1230 | match == ITERATOR_PATHLIST_IS_PARENT) | |
1231 | is_dir = true; | |
9bea03ce RB |
1232 | } |
1233 | ||
0e0589fc ET |
1234 | *is_dir_out = is_dir; |
1235 | *match_out = match; | |
1236 | return true; | |
b6c93aef RB |
1237 | } |
1238 | ||
0e0589fc ET |
1239 | GIT_INLINE(bool) filesystem_iterator_is_dot_git( |
1240 | filesystem_iterator *iter, const char *path, size_t path_len) | |
b6c93aef | 1241 | { |
0e0589fc | 1242 | size_t len; |
169dc616 | 1243 | |
0e0589fc ET |
1244 | if (!iterator__ignore_dot_git(&iter->base)) |
1245 | return false; | |
3b4c401a | 1246 | |
0e0589fc ET |
1247 | if ((len = path_len) < 4) |
1248 | return false; | |
b2414661 | 1249 | |
0e0589fc ET |
1250 | if (path[len - 1] == '/') |
1251 | len--; | |
9bea03ce | 1252 | |
0e0589fc ET |
1253 | if (git__tolower(path[len - 1]) != 't' || |
1254 | git__tolower(path[len - 2]) != 'i' || | |
1255 | git__tolower(path[len - 3]) != 'g' || | |
1256 | git__tolower(path[len - 4]) != '.') | |
1257 | return false; | |
41a82592 | 1258 | |
0e0589fc | 1259 | return (len == 4 || path[len - 5] == '/'); |
b6c93aef RB |
1260 | } |
1261 | ||
ac3d33df JK |
1262 | static int filesystem_iterator_entry_hash( |
1263 | filesystem_iterator *iter, | |
1264 | filesystem_iterator_entry *entry) | |
1265 | { | |
1266 | git_buf fullpath = GIT_BUF_INIT; | |
1267 | int error; | |
1268 | ||
1269 | if (S_ISDIR(entry->st.st_mode)) { | |
1270 | memset(&entry->id, 0, GIT_OID_RAWSZ); | |
1271 | return 0; | |
1272 | } | |
1273 | ||
0c9c969a | 1274 | if (iter->base.type == GIT_ITERATOR_WORKDIR) |
ac3d33df JK |
1275 | return git_repository_hashfile(&entry->id, |
1276 | iter->base.repo, entry->path, GIT_OBJECT_BLOB, NULL); | |
1277 | ||
1278 | if (!(error = git_buf_joinpath(&fullpath, iter->root, entry->path))) | |
1279 | error = git_odb_hashfile(&entry->id, fullpath.ptr, GIT_OBJECT_BLOB); | |
1280 | ||
1281 | git_buf_dispose(&fullpath); | |
1282 | return error; | |
1283 | } | |
1284 | ||
1285 | static int filesystem_iterator_entry_init( | |
1286 | filesystem_iterator_entry **out, | |
1287 | filesystem_iterator *iter, | |
0e0589fc ET |
1288 | filesystem_iterator_frame *frame, |
1289 | const char *path, | |
1290 | size_t path_len, | |
1291 | struct stat *statbuf, | |
1292 | iterator_pathlist_search_t pathlist_match) | |
7ef005f1 | 1293 | { |
0e0589fc ET |
1294 | filesystem_iterator_entry *entry; |
1295 | size_t entry_size; | |
ac3d33df JK |
1296 | int error = 0; |
1297 | ||
1298 | *out = NULL; | |
7ef005f1 | 1299 | |
0e0589fc ET |
1300 | /* Make sure to append two bytes, one for the path's null |
1301 | * termination, one for a possible trailing '/' for folders. | |
1302 | */ | |
ac3d33df JK |
1303 | GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, |
1304 | sizeof(filesystem_iterator_entry), path_len); | |
1305 | GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, entry_size, 2); | |
1306 | ||
1307 | entry = git_pool_malloc(&frame->entry_pool, entry_size); | |
1308 | GIT_ERROR_CHECK_ALLOC(entry); | |
7ef005f1 | 1309 | |
0e0589fc ET |
1310 | entry->path_len = path_len; |
1311 | entry->match = pathlist_match; | |
1312 | memcpy(entry->path, path, path_len); | |
1313 | memcpy(&entry->st, statbuf, sizeof(struct stat)); | |
ff0ddfa4 | 1314 | |
0e0589fc ET |
1315 | /* Suffix directory paths with a '/' */ |
1316 | if (S_ISDIR(entry->st.st_mode)) | |
1317 | entry->path[entry->path_len++] = '/'; | |
ff0ddfa4 | 1318 | |
0e0589fc | 1319 | entry->path[entry->path_len] = '\0'; |
ff0ddfa4 | 1320 | |
ac3d33df JK |
1321 | if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) |
1322 | error = filesystem_iterator_entry_hash(iter, entry); | |
1323 | ||
1324 | if (!error) | |
1325 | *out = entry; | |
1326 | ||
1327 | return error; | |
fc57471a | 1328 | } |
71f85226 | 1329 | |
0e0589fc ET |
1330 | static int filesystem_iterator_frame_push( |
1331 | filesystem_iterator *iter, | |
1332 | filesystem_iterator_entry *frame_entry) | |
fc57471a | 1333 | { |
0e0589fc ET |
1334 | filesystem_iterator_frame *new_frame = NULL; |
1335 | git_path_diriter diriter = GIT_PATH_DIRITER_INIT; | |
1336 | git_buf root = GIT_BUF_INIT; | |
1337 | const char *path; | |
1338 | filesystem_iterator_entry *entry; | |
1339 | struct stat statbuf; | |
1340 | size_t path_len; | |
1341 | int error; | |
71f85226 | 1342 | |
0e0589fc | 1343 | if (iter->frames.size == FILESYSTEM_MAX_DEPTH) { |
ac3d33df | 1344 | git_error_set(GIT_ERROR_REPOSITORY, |
c77a55a9 | 1345 | "directory nesting too deep (%"PRIuZ")", iter->frames.size); |
0e0589fc | 1346 | return -1; |
fc57471a | 1347 | } |
71f85226 | 1348 | |
0e0589fc | 1349 | new_frame = git_array_alloc(iter->frames); |
ac3d33df | 1350 | GIT_ERROR_CHECK_ALLOC(new_frame); |
ff0ddfa4 | 1351 | |
0e0589fc | 1352 | memset(new_frame, 0, sizeof(filesystem_iterator_frame)); |
ff0ddfa4 | 1353 | |
0e0589fc ET |
1354 | if (frame_entry) |
1355 | git_buf_joinpath(&root, iter->root, frame_entry->path); | |
ff0ddfa4 | 1356 | else |
0e0589fc | 1357 | git_buf_puts(&root, iter->root); |
ff0ddfa4 | 1358 | |
0e0589fc ET |
1359 | if (git_buf_oom(&root)) { |
1360 | error = -1; | |
1361 | goto done; | |
1362 | } | |
1363 | ||
1364 | new_frame->path_len = frame_entry ? frame_entry->path_len : 0; | |
7ef005f1 | 1365 | |
12786e0f | 1366 | /* Any error here is equivalent to the dir not existing, skip over it */ |
4a0dbeb0 | 1367 | if ((error = git_path_diriter_init( |
0e0589fc | 1368 | &diriter, root.ptr, iter->dirload_flags)) < 0) { |
12786e0f | 1369 | error = GIT_ENOTFOUND; |
7ef005f1 | 1370 | goto done; |
12786e0f | 1371 | } |
7ef005f1 | 1372 | |
0e0589fc ET |
1373 | if ((error = git_vector_init(&new_frame->entries, 64, |
1374 | iterator__ignore_case(&iter->base) ? | |
1375 | filesystem_iterator_entry_cmp_icase : | |
1376 | filesystem_iterator_entry_cmp)) < 0) | |
1377 | goto done; | |
1378 | ||
1379 | git_pool_init(&new_frame->entry_pool, 1); | |
1380 | ||
1381 | /* check if this directory is ignored */ | |
1382 | filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame); | |
1383 | ||
5c387b6c | 1384 | while ((error = git_path_diriter_next(&diriter)) == 0) { |
0e0589fc ET |
1385 | iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL; |
1386 | bool dir_expected = false; | |
1387 | ||
7ef005f1 ET |
1388 | if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) |
1389 | goto done; | |
1390 | ||
0e0589fc | 1391 | assert(path_len > iter->root_len); |
7ef005f1 ET |
1392 | |
1393 | /* remove the prefix if requested */ | |
0e0589fc ET |
1394 | path += iter->root_len; |
1395 | path_len -= iter->root_len; | |
7ef005f1 | 1396 | |
0e0589fc ET |
1397 | /* examine start / end and the pathlist to see if this path is in it. |
1398 | * note that since we haven't yet stat'ed the path, we cannot know | |
1399 | * whether it's a directory yet or not, so this can give us an | |
1400 | * expected type (S_IFDIR or S_IFREG) that we should examine) | |
ef206124 | 1401 | */ |
0e0589fc ET |
1402 | if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match, |
1403 | iter, frame_entry, path, path_len)) | |
ef206124 ET |
1404 | continue; |
1405 | ||
0e0589fc ET |
1406 | /* TODO: don't need to stat if assume unchanged for this path and |
1407 | * we have an index, we can just copy the data out of it. | |
7ef005f1 | 1408 | */ |
7ef005f1 | 1409 | |
0e0589fc ET |
1410 | if ((error = git_path_diriter_stat(&statbuf, &diriter)) < 0) { |
1411 | /* file was removed between readdir and lstat */ | |
1412 | if (error == GIT_ENOTFOUND) | |
1413 | continue; | |
7ef005f1 | 1414 | |
0e0589fc ET |
1415 | /* treat the file as unreadable */ |
1416 | memset(&statbuf, 0, sizeof(statbuf)); | |
1417 | statbuf.st_mode = GIT_FILEMODE_UNREADABLE; | |
ed1c6446 | 1418 | |
0e0589fc ET |
1419 | error = 0; |
1420 | } | |
7ef005f1 | 1421 | |
0e0589fc | 1422 | iter->base.stat_calls++; |
ef206124 | 1423 | |
0e0589fc ET |
1424 | /* Ignore wacky things in the filesystem */ |
1425 | if (!S_ISDIR(statbuf.st_mode) && | |
1426 | !S_ISREG(statbuf.st_mode) && | |
1427 | !S_ISLNK(statbuf.st_mode) && | |
1428 | statbuf.st_mode != GIT_FILEMODE_UNREADABLE) | |
1429 | continue; | |
7ef005f1 | 1430 | |
0e0589fc | 1431 | if (filesystem_iterator_is_dot_git(iter, path, path_len)) |
7ef005f1 | 1432 | continue; |
0e0589fc ET |
1433 | |
1434 | /* convert submodules to GITLINK and remove trailing slashes */ | |
1435 | if (S_ISDIR(statbuf.st_mode)) { | |
1436 | bool submodule = false; | |
1437 | ||
9eb9e5fa ET |
1438 | if ((error = filesystem_iterator_is_submodule(&submodule, |
1439 | iter, path, path_len)) < 0) | |
0e0589fc ET |
1440 | goto done; |
1441 | ||
1442 | if (submodule) | |
1443 | statbuf.st_mode = GIT_FILEMODE_COMMIT; | |
7ef005f1 ET |
1444 | } |
1445 | ||
0e0589fc | 1446 | /* Ensure that the pathlist entry lines up with what we expected */ |
d47f7e1c | 1447 | else if (dir_expected) |
0e0589fc ET |
1448 | continue; |
1449 | ||
ac3d33df JK |
1450 | if ((error = filesystem_iterator_entry_init(&entry, |
1451 | iter, new_frame, path, path_len, &statbuf, pathlist_match)) < 0) | |
1452 | goto done; | |
0e0589fc ET |
1453 | |
1454 | git_vector_insert(&new_frame->entries, entry); | |
7ef005f1 ET |
1455 | } |
1456 | ||
1457 | if (error == GIT_ITEROVER) | |
1458 | error = 0; | |
1459 | ||
1460 | /* sort now that directory suffix is added */ | |
0e0589fc | 1461 | git_vector_sort(&new_frame->entries); |
7ef005f1 ET |
1462 | |
1463 | done: | |
0e0589fc ET |
1464 | if (error < 0) |
1465 | git_array_pop(iter->frames); | |
1466 | ||
ac3d33df | 1467 | git_buf_dispose(&root); |
7ef005f1 ET |
1468 | git_path_diriter_free(&diriter); |
1469 | return error; | |
1470 | } | |
1471 | ||
0e0589fc | 1472 | GIT_INLINE(void) filesystem_iterator_frame_pop(filesystem_iterator *iter) |
ff0ddfa4 | 1473 | { |
0e0589fc | 1474 | filesystem_iterator_frame *frame; |
ff0ddfa4 | 1475 | |
0e0589fc | 1476 | assert(iter->frames.size); |
3b259cbd | 1477 | |
0e0589fc ET |
1478 | frame = git_array_pop(iter->frames); |
1479 | filesystem_iterator_frame_pop_ignores(iter); | |
3b259cbd | 1480 | |
0e0589fc ET |
1481 | git_pool_clear(&frame->entry_pool); |
1482 | git_vector_free(&frame->entries); | |
1483 | } | |
cee695ae | 1484 | |
0e0589fc ET |
1485 | static void filesystem_iterator_set_current( |
1486 | filesystem_iterator *iter, | |
1487 | filesystem_iterator_entry *entry) | |
1488 | { | |
ac3d33df JK |
1489 | /* |
1490 | * Index entries are limited to 32 bit timestamps. We can safely | |
1491 | * cast this since workdir times are only used in the cache; any | |
1492 | * mismatch will cause a hash recomputation which is unfortunate | |
1493 | * but affects only people who set their filetimes to 2038. | |
1494 | * (Same with the file size.) | |
1495 | */ | |
1496 | iter->entry.ctime.seconds = (int32_t)entry->st.st_ctime; | |
1497 | iter->entry.mtime.seconds = (int32_t)entry->st.st_mtime; | |
eae0bfdc | 1498 | |
ac3d33df | 1499 | #if defined(GIT_USE_NSEC) |
eae0bfdc | 1500 | iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; |
0e0589fc | 1501 | iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec; |
eae0bfdc PP |
1502 | #else |
1503 | iter->entry.ctime.nanoseconds = 0; | |
1504 | iter->entry.mtime.nanoseconds = 0; | |
1505 | #endif | |
ff0ddfa4 | 1506 | |
0e0589fc ET |
1507 | iter->entry.dev = entry->st.st_dev; |
1508 | iter->entry.ino = entry->st.st_ino; | |
1509 | iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode); | |
1510 | iter->entry.uid = entry->st.st_uid; | |
1511 | iter->entry.gid = entry->st.st_gid; | |
ac3d33df JK |
1512 | iter->entry.file_size = (uint32_t)entry->st.st_size; |
1513 | ||
1514 | if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) | |
1515 | git_oid_cpy(&iter->entry.id, &entry->id); | |
ff0ddfa4 | 1516 | |
0e0589fc | 1517 | iter->entry.path = entry->path; |
71f85226 | 1518 | |
0e0589fc | 1519 | iter->current_is_ignored = GIT_IGNORE_UNCHECKED; |
ff0ddfa4 RB |
1520 | } |
1521 | ||
0e0589fc ET |
1522 | static int filesystem_iterator_current( |
1523 | const git_index_entry **out, git_iterator *i) | |
ff0ddfa4 | 1524 | { |
0c9c969a | 1525 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
cee695ae | 1526 | |
0e0589fc ET |
1527 | if (!iterator__has_been_accessed(i)) |
1528 | return iter->base.cb->advance(out, i); | |
cee695ae | 1529 | |
0e0589fc ET |
1530 | if (!iter->frames.size) { |
1531 | *out = NULL; | |
1532 | return GIT_ITEROVER; | |
1533 | } | |
ff0ddfa4 | 1534 | |
0e0589fc ET |
1535 | *out = &iter->entry; |
1536 | return 0; | |
ff0ddfa4 RB |
1537 | } |
1538 | ||
eae0bfdc PP |
1539 | static int filesystem_iterator_is_dir( |
1540 | bool *is_dir, | |
1541 | const filesystem_iterator *iter, | |
1542 | const filesystem_iterator_entry *entry) | |
1543 | { | |
1544 | struct stat st; | |
1545 | git_buf fullpath = GIT_BUF_INIT; | |
1546 | int error = 0; | |
1547 | ||
1548 | if (S_ISDIR(entry->st.st_mode)) { | |
1549 | *is_dir = 1; | |
1550 | goto done; | |
1551 | } | |
1552 | ||
1553 | if (!iterator__descend_symlinks(iter) || !S_ISLNK(entry->st.st_mode)) { | |
1554 | *is_dir = 0; | |
1555 | goto done; | |
1556 | } | |
1557 | ||
1558 | if ((error = git_buf_joinpath(&fullpath, iter->root, entry->path)) < 0 || | |
1559 | (error = p_stat(fullpath.ptr, &st)) < 0) | |
1560 | goto done; | |
1561 | ||
1562 | *is_dir = S_ISDIR(st.st_mode); | |
1563 | ||
1564 | done: | |
ac3d33df | 1565 | git_buf_dispose(&fullpath); |
eae0bfdc PP |
1566 | return error; |
1567 | } | |
1568 | ||
0e0589fc ET |
1569 | static int filesystem_iterator_advance( |
1570 | const git_index_entry **out, git_iterator *i) | |
ff0ddfa4 | 1571 | { |
0c9c969a | 1572 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
eae0bfdc | 1573 | bool is_dir; |
ff0ddfa4 | 1574 | int error = 0; |
ff0ddfa4 | 1575 | |
0e0589fc | 1576 | iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; |
ff0ddfa4 | 1577 | |
0e0589fc ET |
1578 | /* examine filesystem entries until we find the next one to return */ |
1579 | while (true) { | |
1580 | filesystem_iterator_frame *frame; | |
1581 | filesystem_iterator_entry *entry; | |
ff0ddfa4 | 1582 | |
0e0589fc ET |
1583 | if ((frame = filesystem_iterator_current_frame(iter)) == NULL) { |
1584 | error = GIT_ITEROVER; | |
1585 | break; | |
1586 | } | |
ff0ddfa4 | 1587 | |
0e0589fc ET |
1588 | /* no more entries in this frame. pop the frame out */ |
1589 | if (frame->next_idx == frame->entries.length) { | |
1590 | filesystem_iterator_frame_pop(iter); | |
1591 | continue; | |
1592 | } | |
cee695ae | 1593 | |
0e0589fc ET |
1594 | /* we have more entries in the current frame, that's our next entry */ |
1595 | entry = frame->entries.contents[frame->next_idx]; | |
1596 | frame->next_idx++; | |
ff0ddfa4 | 1597 | |
eae0bfdc PP |
1598 | if ((error = filesystem_iterator_is_dir(&is_dir, iter, entry)) < 0) |
1599 | break; | |
1600 | ||
1601 | if (is_dir) { | |
0e0589fc ET |
1602 | if (iterator__do_autoexpand(iter)) { |
1603 | error = filesystem_iterator_frame_push(iter, entry); | |
ff0ddfa4 | 1604 | |
0e0589fc ET |
1605 | /* may get GIT_ENOTFOUND due to races or permission problems |
1606 | * that we want to quietly swallow | |
1607 | */ | |
1608 | if (error == GIT_ENOTFOUND) | |
1609 | continue; | |
1610 | else if (error < 0) | |
1611 | break; | |
1612 | } | |
ff0ddfa4 | 1613 | |
0e0589fc ET |
1614 | if (!iterator__include_trees(iter)) |
1615 | continue; | |
1616 | } | |
ff0ddfa4 | 1617 | |
0e0589fc ET |
1618 | filesystem_iterator_set_current(iter, entry); |
1619 | break; | |
ff0ddfa4 | 1620 | } |
0e0589fc ET |
1621 | |
1622 | if (out) | |
1623 | *out = (error == 0) ? &iter->entry : NULL; | |
1624 | ||
1625 | return error; | |
26d7cf6e | 1626 | } |
ff0ddfa4 | 1627 | |
0e0589fc ET |
1628 | static int filesystem_iterator_advance_into( |
1629 | const git_index_entry **out, git_iterator *i) | |
26d7cf6e | 1630 | { |
0c9c969a | 1631 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
0e0589fc ET |
1632 | filesystem_iterator_frame *frame; |
1633 | filesystem_iterator_entry *prev_entry; | |
26d7cf6e ET |
1634 | int error; |
1635 | ||
0e0589fc ET |
1636 | if (out) |
1637 | *out = NULL; | |
ff0ddfa4 | 1638 | |
0e0589fc ET |
1639 | if ((frame = filesystem_iterator_current_frame(iter)) == NULL) |
1640 | return GIT_ITEROVER; | |
ff0ddfa4 | 1641 | |
0e0589fc ET |
1642 | /* get the last seen entry */ |
1643 | prev_entry = filesystem_iterator_current_entry(frame); | |
ff0ddfa4 | 1644 | |
0e0589fc ET |
1645 | /* it's legal to call advance_into when auto-expand is on. in this case, |
1646 | * we will have pushed a new (empty) frame on to the stack for this | |
1647 | * new directory. since it's empty, its current_entry should be null. | |
1648 | */ | |
1649 | assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); | |
71f85226 | 1650 | |
0e0589fc ET |
1651 | if (prev_entry) { |
1652 | if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT && | |
1653 | !S_ISDIR(prev_entry->st.st_mode)) | |
1654 | return 0; | |
cee695ae | 1655 | |
0e0589fc | 1656 | if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0) |
71f85226 | 1657 | return error; |
71f85226 RB |
1658 | } |
1659 | ||
0e0589fc ET |
1660 | /* we've advanced into the directory in question, let advance |
1661 | * find the first entry | |
1662 | */ | |
1663 | return filesystem_iterator_advance(out, i); | |
71f85226 RB |
1664 | } |
1665 | ||
0e0589fc | 1666 | int git_iterator_current_workdir_path(git_buf **out, git_iterator *i) |
ff0ddfa4 | 1667 | { |
0c9c969a | 1668 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
0e0589fc | 1669 | const git_index_entry *entry; |
ff0ddfa4 | 1670 | |
0c9c969a UG |
1671 | if (i->type != GIT_ITERATOR_FS && |
1672 | i->type != GIT_ITERATOR_WORKDIR) { | |
0e0589fc ET |
1673 | *out = NULL; |
1674 | return 0; | |
1675 | } | |
684b35c4 | 1676 | |
0e0589fc | 1677 | git_buf_truncate(&iter->current_path, iter->root_len); |
ff0ddfa4 | 1678 | |
0e0589fc ET |
1679 | if (git_iterator_current(&entry, i) < 0 || |
1680 | git_buf_puts(&iter->current_path, entry->path) < 0) | |
1681 | return -1; | |
ff0ddfa4 | 1682 | |
0e0589fc ET |
1683 | *out = &iter->current_path; |
1684 | return 0; | |
1685 | } | |
cee695ae | 1686 | |
0e0589fc ET |
1687 | GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry) |
1688 | { | |
1689 | #if defined(GIT_WIN32) && !defined(__MINGW32__) | |
1690 | return (entry && entry->mode) ? | |
1691 | (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) : | |
1692 | GIT_DIR_FLAG_UNKNOWN; | |
1693 | #else | |
1694 | GIT_UNUSED(entry); | |
1695 | return GIT_DIR_FLAG_UNKNOWN; | |
1696 | #endif | |
ff0ddfa4 RB |
1697 | } |
1698 | ||
0e0589fc | 1699 | static void filesystem_iterator_update_ignored(filesystem_iterator *iter) |
684b35c4 | 1700 | { |
0e0589fc ET |
1701 | filesystem_iterator_frame *frame; |
1702 | git_dir_flag dir_flag = entry_dir_flag(&iter->entry); | |
684b35c4 | 1703 | |
0e0589fc ET |
1704 | if (git_ignore__lookup(&iter->current_is_ignored, |
1705 | &iter->ignores, iter->entry.path, dir_flag) < 0) { | |
ac3d33df | 1706 | git_error_clear(); |
0e0589fc ET |
1707 | iter->current_is_ignored = GIT_IGNORE_NOTFOUND; |
1708 | } | |
684b35c4 | 1709 | |
0e0589fc ET |
1710 | /* use ignore from containing frame stack */ |
1711 | if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) { | |
1712 | frame = filesystem_iterator_current_frame(iter); | |
1713 | iter->current_is_ignored = frame->is_ignored; | |
1714 | } | |
684b35c4 ET |
1715 | } |
1716 | ||
0e0589fc ET |
1717 | GIT_INLINE(bool) filesystem_iterator_current_is_ignored( |
1718 | filesystem_iterator *iter) | |
ff0ddfa4 | 1719 | { |
0e0589fc ET |
1720 | if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED) |
1721 | filesystem_iterator_update_ignored(iter); | |
ff0ddfa4 | 1722 | |
0e0589fc | 1723 | return (iter->current_is_ignored == GIT_IGNORE_TRUE); |
ff0ddfa4 RB |
1724 | } |
1725 | ||
0e0589fc | 1726 | bool git_iterator_current_is_ignored(git_iterator *i) |
ff0ddfa4 | 1727 | { |
0c9c969a UG |
1728 | filesystem_iterator *iter = NULL; |
1729 | ||
1730 | if (i->type != GIT_ITERATOR_WORKDIR) | |
0e0589fc | 1731 | return false; |
ff0ddfa4 | 1732 | |
0c9c969a UG |
1733 | iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
1734 | ||
1735 | return filesystem_iterator_current_is_ignored(iter); | |
0e0589fc | 1736 | } |
cee695ae | 1737 | |
0e0589fc ET |
1738 | bool git_iterator_current_tree_is_ignored(git_iterator *i) |
1739 | { | |
0c9c969a | 1740 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
0e0589fc | 1741 | filesystem_iterator_frame *frame; |
cee695ae | 1742 | |
0c9c969a | 1743 | if (i->type != GIT_ITERATOR_WORKDIR) |
0e0589fc | 1744 | return false; |
cee695ae | 1745 | |
0e0589fc ET |
1746 | frame = filesystem_iterator_current_frame(iter); |
1747 | return (frame->is_ignored == GIT_IGNORE_TRUE); | |
1748 | } | |
ff0ddfa4 | 1749 | |
0e0589fc ET |
1750 | static int filesystem_iterator_advance_over( |
1751 | const git_index_entry **out, | |
1752 | git_iterator_status_t *status, | |
1753 | git_iterator *i) | |
1754 | { | |
0c9c969a | 1755 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
0e0589fc ET |
1756 | filesystem_iterator_frame *current_frame; |
1757 | filesystem_iterator_entry *current_entry; | |
1758 | const git_index_entry *entry = NULL; | |
1759 | const char *base; | |
1760 | int error = 0; | |
ff0ddfa4 | 1761 | |
0e0589fc ET |
1762 | *out = NULL; |
1763 | *status = GIT_ITERATOR_STATUS_NORMAL; | |
ff0ddfa4 | 1764 | |
0e0589fc | 1765 | assert(iterator__has_been_accessed(i)); |
ff0ddfa4 | 1766 | |
0e0589fc ET |
1767 | current_frame = filesystem_iterator_current_frame(iter); |
1768 | assert(current_frame); | |
1769 | current_entry = filesystem_iterator_current_entry(current_frame); | |
1770 | assert(current_entry); | |
26d7cf6e | 1771 | |
0e0589fc ET |
1772 | if ((error = git_iterator_current(&entry, i)) < 0) |
1773 | return error; | |
26d7cf6e | 1774 | |
0e0589fc ET |
1775 | if (!S_ISDIR(entry->mode)) { |
1776 | if (filesystem_iterator_current_is_ignored(iter)) | |
1777 | *status = GIT_ITERATOR_STATUS_IGNORED; | |
26d7cf6e | 1778 | |
0e0589fc | 1779 | return filesystem_iterator_advance(out, i); |
cee695ae | 1780 | } |
ff0ddfa4 | 1781 | |
0e0589fc ET |
1782 | git_buf_clear(&iter->tmp_buf); |
1783 | if ((error = git_buf_puts(&iter->tmp_buf, entry->path)) < 0) | |
1784 | return error; | |
ff0ddfa4 | 1785 | |
0e0589fc ET |
1786 | base = iter->tmp_buf.ptr; |
1787 | ||
1788 | /* scan inside the directory looking for files. if we find nothing, | |
1789 | * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to | |
1790 | * IGNORED. if we find a real actual item, upgrade all the way to NORMAL | |
1791 | * and then stop. | |
1792 | * | |
1793 | * however, if we're here looking for a pathlist item (but are not | |
1794 | * actually in the pathlist ourselves) then start at FILTERED instead of | |
1795 | * EMPTY. callers then know that this path was not something they asked | |
1796 | * about. | |
1797 | */ | |
1798 | *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ? | |
1799 | GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY; | |
ff0ddfa4 | 1800 | |
0e0589fc ET |
1801 | while (entry && !iter->base.prefixcomp(entry->path, base)) { |
1802 | if (filesystem_iterator_current_is_ignored(iter)) { | |
1803 | /* if we found an explicitly ignored item, then update from | |
1804 | * EMPTY to IGNORED | |
1805 | */ | |
1806 | *status = GIT_ITERATOR_STATUS_IGNORED; | |
1807 | } else if (S_ISDIR(entry->mode)) { | |
1808 | error = filesystem_iterator_advance_into(&entry, i); | |
1809 | ||
1810 | if (!error) | |
1811 | continue; | |
ff0ddfa4 | 1812 | |
0e0589fc ET |
1813 | /* this directory disappeared, ignore it */ |
1814 | else if (error == GIT_ENOTFOUND) | |
1815 | error = 0; | |
219d3457 | 1816 | |
0e0589fc ET |
1817 | /* a real error occurred */ |
1818 | else | |
1819 | break; | |
cee695ae | 1820 | } else { |
0e0589fc ET |
1821 | /* we found a non-ignored item, treat parent as untracked */ |
1822 | *status = GIT_ITERATOR_STATUS_NORMAL; | |
1823 | break; | |
cee695ae | 1824 | } |
0e0589fc ET |
1825 | |
1826 | if ((error = git_iterator_advance(&entry, i)) < 0) | |
1827 | break; | |
1828 | } | |
1829 | ||
1830 | /* wrap up scan back to base directory */ | |
1831 | while (entry && !iter->base.prefixcomp(entry->path, base)) { | |
1832 | if ((error = git_iterator_advance(&entry, i)) < 0) | |
1833 | break; | |
ff0ddfa4 RB |
1834 | } |
1835 | ||
0e0589fc ET |
1836 | if (!error) |
1837 | *out = entry; | |
1838 | ||
ff0ddfa4 RB |
1839 | return error; |
1840 | } | |
1841 | ||
0e0589fc | 1842 | static void filesystem_iterator_clear(filesystem_iterator *iter) |
71f85226 | 1843 | { |
0e0589fc ET |
1844 | while (iter->frames.size) |
1845 | filesystem_iterator_frame_pop(iter); | |
ff0ddfa4 | 1846 | |
0e0589fc ET |
1847 | git_array_clear(iter->frames); |
1848 | git_ignore__free(&iter->ignores); | |
71f85226 | 1849 | |
ac3d33df | 1850 | git_buf_dispose(&iter->tmp_buf); |
71f85226 | 1851 | |
0e0589fc | 1852 | iterator_clear(&iter->base); |
71f85226 | 1853 | } |
9bea03ce | 1854 | |
0e0589fc | 1855 | static int filesystem_iterator_init(filesystem_iterator *iter) |
d46b0a04 | 1856 | { |
0e0589fc | 1857 | int error; |
b6c93aef | 1858 | |
0e0589fc ET |
1859 | if (iterator__honor_ignores(&iter->base) && |
1860 | (error = git_ignore__for_path(iter->base.repo, | |
1861 | ".gitignore", &iter->ignores)) < 0) | |
1862 | return error; | |
91e7d263 | 1863 | |
0e0589fc ET |
1864 | if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0) |
1865 | return error; | |
91e7d263 | 1866 | |
0e0589fc | 1867 | iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; |
91e7d263 | 1868 | |
0e0589fc | 1869 | return 0; |
ec40b7f9 PK |
1870 | } |
1871 | ||
0e0589fc | 1872 | static int filesystem_iterator_reset(git_iterator *i) |
62a617dc | 1873 | { |
0c9c969a | 1874 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
62a617dc | 1875 | |
0e0589fc ET |
1876 | filesystem_iterator_clear(iter); |
1877 | return filesystem_iterator_init(iter); | |
1878 | } | |
62a617dc | 1879 | |
0e0589fc ET |
1880 | static void filesystem_iterator_free(git_iterator *i) |
1881 | { | |
0c9c969a | 1882 | filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); |
f5c874a4 | 1883 | git__free(iter->root); |
ac3d33df | 1884 | git_buf_dispose(&iter->current_path); |
97054833 | 1885 | git_tree_free(iter->tree); |
f5c874a4 CMN |
1886 | if (iter->index) |
1887 | git_index_snapshot_release(&iter->index_snapshot, iter->index); | |
0e0589fc | 1888 | filesystem_iterator_clear(iter); |
4c09e19a W |
1889 | } |
1890 | ||
0e0589fc ET |
1891 | static int iterator_for_filesystem( |
1892 | git_iterator **out, | |
1893 | git_repository *repo, | |
1894 | const char *root, | |
1895 | git_index *index, | |
1896 | git_tree *tree, | |
0c9c969a | 1897 | git_iterator_t type, |
0e0589fc | 1898 | git_iterator_options *options) |
b6c93aef | 1899 | { |
0e0589fc ET |
1900 | filesystem_iterator *iter; |
1901 | size_t root_len; | |
1902 | int error; | |
c856f8c5 | 1903 | |
0e0589fc ET |
1904 | static git_iterator_callbacks callbacks = { |
1905 | filesystem_iterator_current, | |
1906 | filesystem_iterator_advance, | |
1907 | filesystem_iterator_advance_into, | |
1908 | filesystem_iterator_advance_over, | |
1909 | filesystem_iterator_reset, | |
0e0589fc ET |
1910 | filesystem_iterator_free |
1911 | }; | |
4c09e19a | 1912 | |
0e0589fc | 1913 | *out = NULL; |
f554611a | 1914 | |
0e0589fc ET |
1915 | if (root == NULL) |
1916 | return git_iterator_for_nothing(out, options); | |
91e7d263 | 1917 | |
0e0589fc | 1918 | iter = git__calloc(1, sizeof(filesystem_iterator)); |
ac3d33df | 1919 | GIT_ERROR_CHECK_ALLOC(iter); |
f554611a | 1920 | |
7a3f1de5 JH |
1921 | iter->base.type = type; |
1922 | iter->base.cb = &callbacks; | |
1923 | ||
0e0589fc | 1924 | root_len = strlen(root); |
b6c93aef | 1925 | |
0e0589fc | 1926 | iter->root = git__malloc(root_len+2); |
ac3d33df | 1927 | GIT_ERROR_CHECK_ALLOC(iter->root); |
240f4af3 | 1928 | |
0e0589fc | 1929 | memcpy(iter->root, root, root_len); |
c856f8c5 | 1930 | |
0e0589fc ET |
1931 | if (root_len == 0 || root[root_len-1] != '/') { |
1932 | iter->root[root_len] = '/'; | |
1933 | root_len++; | |
c856f8c5 | 1934 | } |
0e0589fc ET |
1935 | iter->root[root_len] = '\0'; |
1936 | iter->root_len = root_len; | |
c856f8c5 | 1937 | |
0e0589fc ET |
1938 | if ((error = git_buf_puts(&iter->current_path, iter->root)) < 0) |
1939 | goto on_error; | |
b6c93aef | 1940 | |
0ef0b71c | 1941 | if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0) |
0e0589fc ET |
1942 | goto on_error; |
1943 | ||
1944 | if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0) | |
1945 | goto on_error; | |
1946 | ||
0ef0b71c | 1947 | if (index && |
0e0589fc ET |
1948 | (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0) |
1949 | goto on_error; | |
1950 | ||
f5c874a4 | 1951 | iter->index = index; |
0e0589fc ET |
1952 | iter->dirload_flags = |
1953 | (iterator__ignore_case(&iter->base) ? GIT_PATH_DIR_IGNORE_CASE : 0) | | |
1954 | (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ? | |
1955 | GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0); | |
9bea03ce | 1956 | |
0e0589fc ET |
1957 | if ((error = filesystem_iterator_init(iter)) < 0) |
1958 | goto on_error; | |
b6c93aef | 1959 | |
0e0589fc | 1960 | *out = &iter->base; |
41a82592 | 1961 | return 0; |
0e0589fc ET |
1962 | |
1963 | on_error: | |
0e0589fc ET |
1964 | git_iterator_free(&iter->base); |
1965 | return error; | |
41a82592 RB |
1966 | } |
1967 | ||
0e0589fc ET |
1968 | int git_iterator_for_filesystem( |
1969 | git_iterator **out, | |
1970 | const char *root, | |
1971 | git_iterator_options *options) | |
b6c93aef | 1972 | { |
0e0589fc | 1973 | return iterator_for_filesystem(out, |
0c9c969a | 1974 | NULL, root, NULL, NULL, GIT_ITERATOR_FS, options); |
b6c93aef RB |
1975 | } |
1976 | ||
9094ae5a | 1977 | int git_iterator_for_workdir_ext( |
fc57471a | 1978 | git_iterator **out, |
41a82592 | 1979 | git_repository *repo, |
9094ae5a | 1980 | const char *repo_workdir, |
62a617dc CMN |
1981 | git_index *index, |
1982 | git_tree *tree, | |
0e0589fc | 1983 | git_iterator_options *given_opts) |
b6c93aef | 1984 | { |
0e0589fc | 1985 | git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT; |
6e5c4af0 | 1986 | |
9094ae5a RB |
1987 | if (!repo_workdir) { |
1988 | if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) | |
1989 | return GIT_EBAREREPO; | |
41a82592 | 1990 | |
0e0589fc | 1991 | repo_workdir = git_repository_workdir(repo); |
62a617dc | 1992 | } |
62a617dc | 1993 | |
0e0589fc ET |
1994 | /* upgrade to a workdir iterator, adding necessary internal flags */ |
1995 | if (given_opts) | |
1996 | memcpy(&options, given_opts, sizeof(git_iterator_options)); | |
62a617dc | 1997 | |
0e0589fc ET |
1998 | options.flags |= GIT_ITERATOR_HONOR_IGNORES | |
1999 | GIT_ITERATOR_IGNORE_DOT_GIT; | |
2fe54afa | 2000 | |
0e0589fc | 2001 | return iterator_for_filesystem(out, |
0c9c969a | 2002 | repo, repo_workdir, index, tree, GIT_ITERATOR_WORKDIR, &options); |
b6c93aef RB |
2003 | } |
2004 | ||
f616a36b | 2005 | |
0e0589fc | 2006 | /* Index iterator */ |
ec40b7f9 | 2007 | |
cc216a01 | 2008 | |
0e0589fc ET |
2009 | typedef struct { |
2010 | git_iterator base; | |
0e0589fc | 2011 | git_vector entries; |
0ef0b71c ET |
2012 | size_t next_idx; |
2013 | ||
2014 | /* the pseudotree entry */ | |
0e0589fc | 2015 | git_index_entry tree_entry; |
0ef0b71c ET |
2016 | git_buf tree_buf; |
2017 | bool skip_tree; | |
2018 | ||
2019 | const git_index_entry *entry; | |
0e0589fc | 2020 | } index_iterator; |
f616a36b | 2021 | |
0ef0b71c ET |
2022 | static int index_iterator_current( |
2023 | const git_index_entry **out, git_iterator *i) | |
ec40b7f9 | 2024 | { |
0ef0b71c | 2025 | index_iterator *iter = (index_iterator *)i; |
ec40b7f9 | 2026 | |
0ef0b71c ET |
2027 | if (!iterator__has_been_accessed(i)) |
2028 | return iter->base.cb->advance(out, i); | |
2029 | ||
2030 | if (iter->entry == NULL) { | |
2031 | *out = NULL; | |
2032 | return GIT_ITEROVER; | |
ec40b7f9 PK |
2033 | } |
2034 | ||
0ef0b71c ET |
2035 | *out = iter->entry; |
2036 | return 0; | |
4b181037 RB |
2037 | } |
2038 | ||
0ef0b71c ET |
2039 | static bool index_iterator_create_pseudotree( |
2040 | const git_index_entry **out, | |
2041 | index_iterator *iter, | |
2042 | const char *path) | |
5cf9875a | 2043 | { |
0ef0b71c ET |
2044 | const char *prev_path, *relative_path, *dirsep; |
2045 | size_t common_len; | |
0e0589fc | 2046 | |
0ef0b71c | 2047 | prev_path = iter->entry ? iter->entry->path : ""; |
5cf9875a | 2048 | |
0ef0b71c ET |
2049 | /* determine if the new path is in a different directory from the old */ |
2050 | common_len = git_path_common_dirlen(prev_path, path); | |
2051 | relative_path = path + common_len; | |
4c09e19a | 2052 | |
0ef0b71c ET |
2053 | if ((dirsep = strchr(relative_path, '/')) == NULL) |
2054 | return false; | |
0e0589fc | 2055 | |
0ef0b71c ET |
2056 | git_buf_clear(&iter->tree_buf); |
2057 | git_buf_put(&iter->tree_buf, path, (dirsep - path) + 1); | |
0e0589fc | 2058 | |
0ef0b71c ET |
2059 | iter->tree_entry.mode = GIT_FILEMODE_TREE; |
2060 | iter->tree_entry.path = iter->tree_buf.ptr; | |
f554611a | 2061 | |
0ef0b71c ET |
2062 | *out = &iter->tree_entry; |
2063 | return true; | |
f554611a RB |
2064 | } |
2065 | ||
0ef0b71c | 2066 | static int index_iterator_skip_pseudotree(index_iterator *iter) |
b6c93aef | 2067 | { |
0ef0b71c ET |
2068 | assert(iterator__has_been_accessed(&iter->base)); |
2069 | assert(S_ISDIR(iter->entry->mode)); | |
220d5a6c | 2070 | |
0ef0b71c ET |
2071 | while (true) { |
2072 | const git_index_entry *next_entry = NULL; | |
0e0589fc | 2073 | |
0ef0b71c ET |
2074 | if (++iter->next_idx >= iter->entries.length) |
2075 | return GIT_ITEROVER; | |
220d5a6c | 2076 | |
0ef0b71c | 2077 | next_entry = iter->entries.contents[iter->next_idx]; |
220d5a6c | 2078 | |
0ef0b71c ET |
2079 | if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path, |
2080 | iter->tree_buf.size) != 0) | |
2081 | break; | |
2082 | } | |
f554611a | 2083 | |
0ef0b71c | 2084 | iter->skip_tree = false; |
0e0589fc | 2085 | return 0; |
f554611a RB |
2086 | } |
2087 | ||
0ef0b71c ET |
2088 | static int index_iterator_advance( |
2089 | const git_index_entry **out, git_iterator *i) | |
f554611a | 2090 | { |
0c9c969a | 2091 | index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); |
0ef0b71c | 2092 | const git_index_entry *entry = NULL; |
d47f7e1c | 2093 | bool is_submodule; |
0ef0b71c | 2094 | int error = 0; |
f554611a | 2095 | |
0ef0b71c | 2096 | iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; |
0e0589fc | 2097 | |
0ef0b71c ET |
2098 | while (true) { |
2099 | if (iter->next_idx >= iter->entries.length) { | |
2100 | error = GIT_ITEROVER; | |
2101 | break; | |
2102 | } | |
220d5a6c | 2103 | |
0ef0b71c ET |
2104 | /* we were not asked to expand this pseudotree. advance over it. */ |
2105 | if (iter->skip_tree) { | |
2106 | index_iterator_skip_pseudotree(iter); | |
2107 | continue; | |
2108 | } | |
0e0589fc | 2109 | |
0ef0b71c | 2110 | entry = iter->entries.contents[iter->next_idx]; |
d47f7e1c | 2111 | is_submodule = S_ISGITLINK(entry->mode); |
0534641d | 2112 | |
d47f7e1c | 2113 | if (!iterator_has_started(&iter->base, entry->path, is_submodule)) { |
0ef0b71c ET |
2114 | iter->next_idx++; |
2115 | continue; | |
2116 | } | |
41a82592 | 2117 | |
db22a91b | 2118 | if (iterator_has_ended(&iter->base, entry->path)) { |
0ef0b71c ET |
2119 | error = GIT_ITEROVER; |
2120 | break; | |
2121 | } | |
41a82592 | 2122 | |
0ef0b71c ET |
2123 | /* if we have a list of paths we're interested in, examine it */ |
2124 | if (!iterator_pathlist_next_is(&iter->base, entry->path)) { | |
2125 | iter->next_idx++; | |
2126 | continue; | |
2127 | } | |
41a82592 | 2128 | |
0ef0b71c ET |
2129 | /* if this is a conflict, skip it unless we're including conflicts */ |
2130 | if (git_index_entry_is_conflict(entry) && | |
2131 | !iterator__include_conflicts(&iter->base)) { | |
2132 | iter->next_idx++; | |
2133 | continue; | |
2134 | } | |
0e0589fc | 2135 | |
0ef0b71c ET |
2136 | /* we've found what will be our next _file_ entry. but if we are |
2137 | * returning trees entries, we may need to return a pseudotree | |
2138 | * entry that will contain this. don't advance over this entry, | |
2139 | * though, we still need to return it on the next `advance`. | |
2140 | */ | |
2141 | if (iterator__include_trees(&iter->base) && | |
2142 | index_iterator_create_pseudotree(&entry, iter, entry->path)) { | |
0e0589fc | 2143 | |
0ef0b71c ET |
2144 | /* Note whether this pseudo tree should be expanded or not */ |
2145 | iter->skip_tree = iterator__dont_autoexpand(&iter->base); | |
2146 | break; | |
0e0589fc | 2147 | } |
0e0589fc | 2148 | |
0ef0b71c ET |
2149 | iter->next_idx++; |
2150 | break; | |
0e0589fc ET |
2151 | } |
2152 | ||
0ef0b71c ET |
2153 | iter->entry = (error == 0) ? entry : NULL; |
2154 | ||
2155 | if (out) | |
2156 | *out = iter->entry; | |
2157 | ||
2158 | return error; | |
41a82592 RB |
2159 | } |
2160 | ||
0ef0b71c ET |
2161 | static int index_iterator_advance_into( |
2162 | const git_index_entry **out, git_iterator *i) | |
dfbff793 | 2163 | { |
0c9c969a | 2164 | index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); |
dfbff793 | 2165 | |
0ef0b71c | 2166 | if (! S_ISDIR(iter->tree_entry.mode)) { |
9eb9e5fa ET |
2167 | if (out) |
2168 | *out = NULL; | |
2169 | ||
0ef0b71c | 2170 | return 0; |
0e0589fc | 2171 | } |
dfbff793 | 2172 | |
0ef0b71c ET |
2173 | iter->skip_tree = false; |
2174 | return index_iterator_advance(out, i); | |
dfbff793 | 2175 | } |
37da3685 | 2176 | |
0ef0b71c ET |
2177 | static int index_iterator_advance_over( |
2178 | const git_index_entry **out, | |
2179 | git_iterator_status_t *status, | |
2180 | git_iterator *i) | |
ff475375 | 2181 | { |
0c9c969a | 2182 | index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); |
0ef0b71c ET |
2183 | const git_index_entry *entry; |
2184 | int error; | |
0e0589fc | 2185 | |
0ef0b71c ET |
2186 | if ((error = index_iterator_current(&entry, i)) < 0) |
2187 | return error; | |
0e0589fc | 2188 | |
0ef0b71c ET |
2189 | if (S_ISDIR(entry->mode)) |
2190 | index_iterator_skip_pseudotree(iter); | |
0e0589fc | 2191 | |
0ef0b71c ET |
2192 | *status = GIT_ITERATOR_STATUS_NORMAL; |
2193 | return index_iterator_advance(out, i); | |
2194 | } | |
0e0589fc | 2195 | |
0ef0b71c ET |
2196 | static void index_iterator_clear(index_iterator *iter) |
2197 | { | |
2198 | iterator_clear(&iter->base); | |
2199 | } | |
0e0589fc | 2200 | |
0ef0b71c ET |
2201 | static int index_iterator_init(index_iterator *iter) |
2202 | { | |
2203 | iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; | |
2204 | iter->next_idx = 0; | |
2205 | iter->skip_tree = false; | |
2206 | return 0; | |
2207 | } | |
ff475375 | 2208 | |
0ef0b71c ET |
2209 | static int index_iterator_reset(git_iterator *i) |
2210 | { | |
0c9c969a | 2211 | index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); |
0e0589fc | 2212 | |
0ef0b71c ET |
2213 | index_iterator_clear(iter); |
2214 | return index_iterator_init(iter); | |
2215 | } | |
0e0589fc | 2216 | |
0ef0b71c | 2217 | static void index_iterator_free(git_iterator *i) |
0e0589fc | 2218 | { |
0c9c969a | 2219 | index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); |
0ef0b71c ET |
2220 | |
2221 | git_index_snapshot_release(&iter->entries, iter->base.index); | |
ac3d33df | 2222 | git_buf_dispose(&iter->tree_buf); |
0e0589fc ET |
2223 | } |
2224 | ||
2225 | int git_iterator_for_index( | |
0ef0b71c | 2226 | git_iterator **out, |
0e0589fc ET |
2227 | git_repository *repo, |
2228 | git_index *index, | |
2229 | git_iterator_options *options) | |
2230 | { | |
0ef0b71c ET |
2231 | index_iterator *iter; |
2232 | int error; | |
37da3685 | 2233 | |
0ef0b71c ET |
2234 | static git_iterator_callbacks callbacks = { |
2235 | index_iterator_current, | |
2236 | index_iterator_advance, | |
2237 | index_iterator_advance_into, | |
2238 | index_iterator_advance_over, | |
2239 | index_iterator_reset, | |
0ef0b71c ET |
2240 | index_iterator_free |
2241 | }; | |
37da3685 | 2242 | |
0ef0b71c | 2243 | *out = NULL; |
0e0589fc | 2244 | |
0ef0b71c ET |
2245 | if (index == NULL) |
2246 | return git_iterator_for_nothing(out, options); | |
37da3685 | 2247 | |
0ef0b71c | 2248 | iter = git__calloc(1, sizeof(index_iterator)); |
ac3d33df | 2249 | GIT_ERROR_CHECK_ALLOC(iter); |
37da3685 | 2250 | |
0c9c969a | 2251 | iter->base.type = GIT_ITERATOR_INDEX; |
0ef0b71c | 2252 | iter->base.cb = &callbacks; |
37da3685 | 2253 | |
0ef0b71c ET |
2254 | if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 || |
2255 | (error = git_index_snapshot_new(&iter->entries, index)) < 0 || | |
2256 | (error = index_iterator_init(iter)) < 0) | |
2257 | goto on_error; | |
37da3685 | 2258 | |
0ef0b71c ET |
2259 | git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ? |
2260 | git_index_entry_icmp : git_index_entry_cmp); | |
2261 | git_vector_sort(&iter->entries); | |
37da3685 | 2262 | |
0ef0b71c | 2263 | *out = &iter->base; |
0e0589fc | 2264 | return 0; |
0ef0b71c ET |
2265 | |
2266 | on_error: | |
2267 | git_iterator_free(&iter->base); | |
2268 | return error; | |
0e0589fc | 2269 | } |
3679ebae | 2270 | |
4a0dbeb0 | 2271 | |
82a1aab6 ET |
2272 | /* Iterator API */ |
2273 | ||
9eb9e5fa ET |
2274 | int git_iterator_reset_range( |
2275 | git_iterator *i, const char *start, const char *end) | |
2276 | { | |
2277 | if (iterator_reset_range(i, start, end) < 0) | |
2278 | return -1; | |
2279 | ||
2280 | return i->cb->reset(i); | |
2281 | } | |
2282 | ||
2283 | void git_iterator_set_ignore_case(git_iterator *i, bool ignore_case) | |
2284 | { | |
2285 | assert(!iterator__has_been_accessed(i)); | |
2286 | iterator_set_ignore_case(i, ignore_case); | |
2287 | } | |
82a1aab6 | 2288 | |
0e0589fc ET |
2289 | void git_iterator_free(git_iterator *iter) |
2290 | { | |
2291 | if (iter == NULL) | |
2292 | return; | |
37da3685 | 2293 | |
0e0589fc | 2294 | iter->cb->free(iter); |
37da3685 | 2295 | |
0e0589fc ET |
2296 | git_vector_free(&iter->pathlist); |
2297 | git__free(iter->start); | |
2298 | git__free(iter->end); | |
37da3685 | 2299 | |
0e0589fc | 2300 | memset(iter, 0, sizeof(*iter)); |
37da3685 | 2301 | |
0e0589fc ET |
2302 | git__free(iter); |
2303 | } | |
2304 | ||
ac3d33df JK |
2305 | int git_iterator_foreach( |
2306 | git_iterator *iterator, | |
2307 | git_iterator_foreach_cb cb, | |
2308 | void *data) | |
2309 | { | |
2310 | const git_index_entry *iterator_item; | |
2311 | int error = 0; | |
2312 | ||
2313 | if ((error = git_iterator_current(&iterator_item, iterator)) < 0) | |
2314 | goto done; | |
2315 | ||
2316 | if ((error = cb(iterator_item, data)) != 0) | |
2317 | goto done; | |
2318 | ||
2319 | while (true) { | |
2320 | if ((error = git_iterator_advance(&iterator_item, iterator)) < 0) | |
2321 | goto done; | |
2322 | ||
2323 | if ((error = cb(iterator_item, data)) != 0) | |
2324 | goto done; | |
2325 | } | |
2326 | ||
2327 | done: | |
2328 | if (error == GIT_ITEROVER) | |
2329 | error = 0; | |
2330 | ||
2331 | return error; | |
2332 | } | |
2333 | ||
8960dc1e ET |
2334 | int git_iterator_walk( |
2335 | git_iterator **iterators, | |
2336 | size_t cnt, | |
2337 | git_iterator_walk_cb cb, | |
2338 | void *data) | |
2339 | { | |
2340 | const git_index_entry **iterator_item; /* next in each iterator */ | |
2341 | const git_index_entry **cur_items; /* current path in each iter */ | |
2342 | const git_index_entry *first_match; | |
8960dc1e ET |
2343 | size_t i, j; |
2344 | int error = 0; | |
2345 | ||
2346 | iterator_item = git__calloc(cnt, sizeof(git_index_entry *)); | |
2347 | cur_items = git__calloc(cnt, sizeof(git_index_entry *)); | |
2348 | ||
ac3d33df JK |
2349 | GIT_ERROR_CHECK_ALLOC(iterator_item); |
2350 | GIT_ERROR_CHECK_ALLOC(cur_items); | |
8960dc1e ET |
2351 | |
2352 | /* Set up the iterators */ | |
2353 | for (i = 0; i < cnt; i++) { | |
2354 | error = git_iterator_current(&iterator_item[i], iterators[i]); | |
2355 | ||
2356 | if (error < 0 && error != GIT_ITEROVER) | |
2357 | goto done; | |
2358 | } | |
2359 | ||
2360 | while (true) { | |
2361 | for (i = 0; i < cnt; i++) | |
2362 | cur_items[i] = NULL; | |
2363 | ||
2364 | first_match = NULL; | |
8960dc1e ET |
2365 | |
2366 | /* Find the next path(s) to consume from each iterator */ | |
2367 | for (i = 0; i < cnt; i++) { | |
2368 | if (iterator_item[i] == NULL) | |
2369 | continue; | |
2370 | ||
2371 | if (first_match == NULL) { | |
2372 | first_match = iterator_item[i]; | |
2373 | cur_items[i] = iterator_item[i]; | |
2374 | } else { | |
2375 | int path_diff = git_index_entry_cmp(iterator_item[i], first_match); | |
2376 | ||
2377 | if (path_diff < 0) { | |
2378 | /* Found an index entry that sorts before the one we're | |
2379 | * looking at. Forget that we've seen the other and | |
2380 | * look at the other iterators for this path. | |
2381 | */ | |
2382 | for (j = 0; j < i; j++) | |
2383 | cur_items[j] = NULL; | |
2384 | ||
2385 | first_match = iterator_item[i]; | |
2386 | cur_items[i] = iterator_item[i]; | |
8960dc1e ET |
2387 | } else if (path_diff == 0) { |
2388 | cur_items[i] = iterator_item[i]; | |
2389 | } | |
2390 | } | |
2391 | } | |
2392 | ||
2393 | if (first_match == NULL) | |
2394 | break; | |
2395 | ||
2396 | if ((error = cb(cur_items, data)) != 0) | |
2397 | goto done; | |
2398 | ||
2399 | /* Advance each iterator that participated */ | |
2400 | for (i = 0; i < cnt; i++) { | |
2401 | if (cur_items[i] == NULL) | |
2402 | continue; | |
2403 | ||
2404 | error = git_iterator_advance(&iterator_item[i], iterators[i]); | |
2405 | ||
2406 | if (error < 0 && error != GIT_ITEROVER) | |
2407 | goto done; | |
2408 | } | |
2409 | } | |
2410 | ||
2411 | done: | |
dd6b24b1 ET |
2412 | git__free((git_index_entry **)iterator_item); |
2413 | git__free((git_index_entry **)cur_items); | |
24fa21f3 | 2414 | |
8960dc1e ET |
2415 | if (error == GIT_ITEROVER) |
2416 | error = 0; | |
2417 | ||
2418 | return error; | |
2419 | } |