]> git.proxmox.com Git - libgit2.git/blob - src/iterator.c
diff: Fix rebase breackage
[libgit2.git] / src / iterator.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8 #include "iterator.h"
9 #include "tree.h"
10 #include "ignore.h"
11 #include "buffer.h"
12
13 typedef struct tree_iterator_frame tree_iterator_frame;
14 struct tree_iterator_frame {
15 tree_iterator_frame *next;
16 git_tree *tree;
17 unsigned int index;
18 };
19
20 typedef struct {
21 git_iterator base;
22 git_repository *repo;
23 tree_iterator_frame *stack;
24 git_index_entry entry;
25 git_buf path;
26 } tree_iterator;
27
28 static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti)
29 {
30 return (ti->stack == NULL) ? NULL :
31 git_tree_entry_byindex(ti->stack->tree, ti->stack->index);
32 }
33
34 static int tree_iterator__current(
35 git_iterator *self, const git_index_entry **entry)
36 {
37 int error;
38 tree_iterator *ti = (tree_iterator *)self;
39 const git_tree_entry *te = tree_iterator__tree_entry(ti);
40
41 *entry = NULL;
42
43 if (te == NULL)
44 return GIT_SUCCESS;
45
46 ti->entry.mode = te->attr;
47 git_oid_cpy(&ti->entry.oid, &te->oid);
48 error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename);
49 if (error < GIT_SUCCESS)
50 return error;
51 ti->entry.path = ti->path.ptr;
52
53 *entry = &ti->entry;
54
55 return GIT_SUCCESS;
56 }
57
58 static int tree_iterator__at_end(git_iterator *self)
59 {
60 return (tree_iterator__tree_entry((tree_iterator *)self) == NULL);
61 }
62
63 static tree_iterator_frame *tree_iterator__alloc_frame(git_tree *tree)
64 {
65 tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame));
66 tf->tree = tree;
67 return tf;
68 }
69
70 static int tree_iterator__expand_tree(tree_iterator *ti)
71 {
72 int error;
73 git_tree *subtree;
74 const git_tree_entry *te = tree_iterator__tree_entry(ti);
75 tree_iterator_frame *tf;
76
77 while (te != NULL && entry_is_tree(te)) {
78 error = git_tree_lookup(&subtree, ti->repo, &te->oid);
79 if (error != GIT_SUCCESS)
80 return error;
81
82 if ((tf = tree_iterator__alloc_frame(subtree)) == NULL)
83 return GIT_ENOMEM;
84
85 tf->next = ti->stack;
86 ti->stack = tf;
87
88 error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename);
89 if (error < GIT_SUCCESS)
90 return error;
91
92 te = tree_iterator__tree_entry(ti);
93 }
94
95 return GIT_SUCCESS;
96 }
97
98 static void tree_iterator__pop_frame(tree_iterator *ti)
99 {
100 tree_iterator_frame *tf = ti->stack;
101 ti->stack = tf->next;
102 if (ti->stack != NULL) /* don't free the initial tree */
103 git_tree_free(tf->tree);
104 git__free(tf);
105 }
106
107 static int tree_iterator__advance(
108 git_iterator *self, const git_index_entry **entry)
109 {
110 int error = GIT_SUCCESS;
111 tree_iterator *ti = (tree_iterator *)self;
112 const git_tree_entry *te = NULL;
113
114 if (entry != NULL)
115 *entry = NULL;
116
117 while (ti->stack != NULL) {
118 /* remove old entry filename */
119 git_buf_rtruncate_at_char(&ti->path, '/');
120
121 te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index);
122 if (te != NULL)
123 break;
124
125 tree_iterator__pop_frame(ti);
126 git_buf_rtruncate_at_char(&ti->path, '/');
127 }
128
129 if (te && entry_is_tree(te))
130 error = tree_iterator__expand_tree(ti);
131
132 if (error == GIT_SUCCESS && entry != NULL)
133 error = tree_iterator__current(self, entry);
134
135 return error;
136 }
137
138 static void tree_iterator__free(git_iterator *self)
139 {
140 tree_iterator *ti = (tree_iterator *)self;
141 while (ti->stack != NULL)
142 tree_iterator__pop_frame(ti);
143 git_buf_free(&ti->path);
144 }
145
146 static int tree_iterator__reset(git_iterator *self)
147 {
148 tree_iterator *ti = (tree_iterator *)self;
149 while (ti->stack && ti->stack->next)
150 tree_iterator__pop_frame(ti);
151 if (ti->stack)
152 ti->stack->index = 0;
153 return tree_iterator__expand_tree(ti);
154 }
155
156 int git_iterator_for_tree(
157 git_repository *repo, git_tree *tree, git_iterator **iter)
158 {
159 int error;
160 tree_iterator *ti = git__calloc(1, sizeof(tree_iterator));
161 if (!ti)
162 return GIT_ENOMEM;
163
164 ti->base.type = GIT_ITERATOR_TREE;
165 ti->base.current = tree_iterator__current;
166 ti->base.at_end = tree_iterator__at_end;
167 ti->base.advance = tree_iterator__advance;
168 ti->base.reset = tree_iterator__reset;
169 ti->base.free = tree_iterator__free;
170 ti->repo = repo;
171 ti->stack = tree_iterator__alloc_frame(tree);
172
173 if ((error = tree_iterator__expand_tree(ti)) < GIT_SUCCESS)
174 git_iterator_free((git_iterator *)ti);
175 else
176 *iter = (git_iterator *)ti;
177
178 return error;
179 }
180
181
182 typedef struct {
183 git_iterator base;
184 git_index *index;
185 unsigned int current;
186 } index_iterator;
187
188 static int index_iterator__current(
189 git_iterator *self, const git_index_entry **entry)
190 {
191 index_iterator *ii = (index_iterator *)self;
192 *entry = git_index_get(ii->index, ii->current);
193 return GIT_SUCCESS;
194 }
195
196 static int index_iterator__at_end(git_iterator *self)
197 {
198 index_iterator *ii = (index_iterator *)self;
199 return (ii->current >= git_index_entrycount(ii->index));
200 }
201
202 static int index_iterator__advance(
203 git_iterator *self, const git_index_entry **entry)
204 {
205 index_iterator *ii = (index_iterator *)self;
206 if (ii->current < git_index_entrycount(ii->index))
207 ii->current++;
208 if (entry)
209 *entry = git_index_get(ii->index, ii->current);
210 return GIT_SUCCESS;
211 }
212
213 static int index_iterator__reset(git_iterator *self)
214 {
215 index_iterator *ii = (index_iterator *)self;
216 ii->current = 0;
217 return GIT_SUCCESS;
218 }
219
220 static void index_iterator__free(git_iterator *self)
221 {
222 index_iterator *ii = (index_iterator *)self;
223 git_index_free(ii->index);
224 ii->index = NULL;
225 }
226
227 int git_iterator_for_index(git_repository *repo, git_iterator **iter)
228 {
229 int error;
230 index_iterator *ii = git__calloc(1, sizeof(index_iterator));
231 if (!ii)
232 return GIT_ENOMEM;
233
234 ii->base.type = GIT_ITERATOR_INDEX;
235 ii->base.current = index_iterator__current;
236 ii->base.at_end = index_iterator__at_end;
237 ii->base.advance = index_iterator__advance;
238 ii->base.reset = index_iterator__reset;
239 ii->base.free = index_iterator__free;
240 ii->current = 0;
241
242 if ((error = git_repository_index(&ii->index, repo)) < GIT_SUCCESS)
243 git__free(ii);
244 else
245 *iter = (git_iterator *)ii;
246 return error;
247 }
248
249
250 typedef struct workdir_iterator_frame workdir_iterator_frame;
251 struct workdir_iterator_frame {
252 workdir_iterator_frame *next;
253 git_vector entries;
254 unsigned int index;
255 };
256
257 typedef struct {
258 git_iterator base;
259 git_repository *repo;
260 size_t root_len;
261 workdir_iterator_frame *stack;
262 git_ignores ignores;
263 git_index_entry entry;
264 git_buf path;
265 int is_ignored;
266 } workdir_iterator;
267
268 static workdir_iterator_frame *workdir_iterator__alloc_frame(void)
269 {
270 workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
271 if (wf == NULL)
272 return wf;
273 if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != GIT_SUCCESS) {
274 git__free(wf);
275 return NULL;
276 }
277 return wf;
278 }
279
280 static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
281 {
282 unsigned int i;
283 git_path_with_stat *path;
284
285 git_vector_foreach(&wf->entries, i, path)
286 git__free(path);
287 git_vector_free(&wf->entries);
288 git__free(wf);
289 }
290
291 static int workdir_iterator__update_entry(workdir_iterator *wi);
292
293 static int workdir_iterator__expand_dir(workdir_iterator *wi)
294 {
295 int error;
296 workdir_iterator_frame *wf = workdir_iterator__alloc_frame();
297 if (wf == NULL)
298 return GIT_ENOMEM;
299
300 error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries);
301 if (error < GIT_SUCCESS || wf->entries.length == 0) {
302 workdir_iterator__free_frame(wf);
303 return GIT_ENOTFOUND;
304 }
305
306 git_vector_sort(&wf->entries);
307 wf->next = wi->stack;
308 wi->stack = wf;
309
310 /* only push new ignores if this is not top level directory */
311 if (wi->stack->next != NULL) {
312 int slash_pos = git_buf_rfind_next(&wi->path, '/');
313 (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]);
314 }
315
316 return workdir_iterator__update_entry(wi);
317 }
318
319 static int workdir_iterator__current(
320 git_iterator *self, const git_index_entry **entry)
321 {
322 workdir_iterator *wi = (workdir_iterator *)self;
323 *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
324 return GIT_SUCCESS;
325 }
326
327 static int workdir_iterator__at_end(git_iterator *self)
328 {
329 return (((workdir_iterator *)self)->entry.path == NULL);
330 }
331
332 static int workdir_iterator__advance(
333 git_iterator *self, const git_index_entry **entry)
334 {
335 int error;
336 workdir_iterator *wi = (workdir_iterator *)self;
337 workdir_iterator_frame *wf;
338 git_path_with_stat *next;
339
340 if (entry != NULL)
341 *entry = NULL;
342
343 if (wi->entry.path == NULL)
344 return GIT_SUCCESS;
345
346 while ((wf = wi->stack) != NULL) {
347 next = git_vector_get(&wf->entries, ++wf->index);
348 if (next != NULL) {
349 if (strcmp(next->path, DOT_GIT "/") == 0)
350 continue;
351 /* else found a good entry */
352 break;
353 }
354
355 /* pop workdir directory stack */
356 wi->stack = wf->next;
357 workdir_iterator__free_frame(wf);
358 git_ignore__pop_dir(&wi->ignores);
359
360 if (wi->stack == NULL) {
361 memset(&wi->entry, 0, sizeof(wi->entry));
362 return GIT_SUCCESS;
363 }
364 }
365
366 error = workdir_iterator__update_entry(wi);
367
368 if (error == GIT_SUCCESS && entry != NULL)
369 error = workdir_iterator__current(self, entry);
370
371 return error;
372 }
373
374 static int workdir_iterator__reset(git_iterator *self)
375 {
376 workdir_iterator *wi = (workdir_iterator *)self;
377 while (wi->stack != NULL && wi->stack->next != NULL) {
378 workdir_iterator_frame *wf = wi->stack;
379 wi->stack = wf->next;
380 workdir_iterator__free_frame(wf);
381 git_ignore__pop_dir(&wi->ignores);
382 }
383 if (wi->stack)
384 wi->stack->index = 0;
385 return GIT_SUCCESS;
386 }
387
388 static void workdir_iterator__free(git_iterator *self)
389 {
390 workdir_iterator *wi = (workdir_iterator *)self;
391
392 while (wi->stack != NULL) {
393 workdir_iterator_frame *wf = wi->stack;
394 wi->stack = wf->next;
395 workdir_iterator__free_frame(wf);
396 }
397
398 git_ignore__free(&wi->ignores);
399 git_buf_free(&wi->path);
400 }
401
402 static int workdir_iterator__update_entry(workdir_iterator *wi)
403 {
404 int error;
405 git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index);
406
407 git_buf_truncate(&wi->path, wi->root_len);
408 error = git_buf_put(&wi->path, ps->path, ps->path_len);
409 if (error < GIT_SUCCESS)
410 return error;
411
412 memset(&wi->entry, 0, sizeof(wi->entry));
413 wi->entry.path = ps->path;
414
415 /* skip over .git directory */
416 if (strcmp(ps->path, DOT_GIT "/") == 0)
417 return workdir_iterator__advance((git_iterator *)wi, NULL);
418
419 /* if there is an error processing the entry, treat as ignored */
420 wi->is_ignored = 1;
421
422 /* TODO: remove shared code for struct stat conversion with index.c */
423 wi->entry.ctime.seconds = (git_time_t)ps->st.st_ctime;
424 wi->entry.mtime.seconds = (git_time_t)ps->st.st_mtime;
425 wi->entry.dev = ps->st.st_rdev;
426 wi->entry.ino = ps->st.st_ino;
427 wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
428 wi->entry.uid = ps->st.st_uid;
429 wi->entry.gid = ps->st.st_gid;
430 wi->entry.file_size = ps->st.st_size;
431
432 /* if this is a file type we don't handle, treat as ignored */
433 if (wi->entry.mode == 0)
434 return GIT_SUCCESS;
435
436 /* okay, we are far enough along to look up real ignore rule */
437 error = git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored);
438 if (error != GIT_SUCCESS)
439 return GIT_SUCCESS;
440
441 /* detect submodules */
442 if (S_ISDIR(wi->entry.mode) &&
443 git_path_contains(&wi->path, DOT_GIT) == true)
444 wi->entry.mode = S_IFGITLINK;
445
446 return GIT_SUCCESS;
447 }
448
449 int git_iterator_for_workdir(git_repository *repo, git_iterator **iter)
450 {
451 int error;
452 workdir_iterator *wi = git__calloc(1, sizeof(workdir_iterator));
453 if (!wi)
454 return GIT_ENOMEM;
455
456 wi->base.type = GIT_ITERATOR_WORKDIR;
457 wi->base.current = workdir_iterator__current;
458 wi->base.at_end = workdir_iterator__at_end;
459 wi->base.advance = workdir_iterator__advance;
460 wi->base.reset = workdir_iterator__reset;
461 wi->base.free = workdir_iterator__free;
462 wi->repo = repo;
463
464 error = git_buf_sets(&wi->path, git_repository_workdir(repo));
465 if (error == GIT_SUCCESS)
466 error = git_path_to_dir(&wi->path);
467 if (error == GIT_SUCCESS)
468 error = git_ignore__for_path(repo, "", &wi->ignores);
469 if (error != GIT_SUCCESS) {
470 git__free(wi);
471 return error;
472 }
473
474 wi->root_len = wi->path.size;
475
476 if ((error = workdir_iterator__expand_dir(wi)) < GIT_SUCCESS)
477 git_iterator_free((git_iterator *)wi);
478 else
479 *iter = (git_iterator *)wi;
480
481 return error;
482 }
483
484
485 int git_iterator_current_tree_entry(
486 git_iterator *iter, const git_tree_entry **tree_entry)
487 {
488 *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL :
489 tree_iterator__tree_entry((tree_iterator *)iter);
490 return GIT_SUCCESS;
491 }
492
493 int git_iterator_current_is_ignored(git_iterator *iter)
494 {
495 return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
496 ((workdir_iterator *)iter)->is_ignored;
497 }
498
499 int git_iterator_advance_into_directory(
500 git_iterator *iter, const git_index_entry **entry)
501 {
502 workdir_iterator *wi = (workdir_iterator *)iter;
503
504 if (iter->type == GIT_ITERATOR_WORKDIR &&
505 wi->entry.path && S_ISDIR(wi->entry.mode))
506 {
507 if (workdir_iterator__expand_dir(wi) < GIT_SUCCESS)
508 /* if error loading or if empty, skip the directory. */
509 return workdir_iterator__advance(iter, entry);
510 }
511
512 return entry ? git_iterator_current(iter, entry) : GIT_SUCCESS;
513 }