dir_iterator: new API for iterating over a directory tree

The iterator interface is modeled on that for references, though no vtable is necessary because there is (so far?) only one type of dir_iterator. There are obviously a lot of features that could easily be added to this class: * Skip/include directory paths in the iteration * Shallow/deep iteration * Letting the caller decide which subdirectories to recurse into (e.g., via a dir_iterator_advance_into() function) * Option to iterate in sorted order * Option to iterate over directory paths before vs. after their contents But these are not needed for the current patch series, so I refrain. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Haggerty committed Jun 18, 2016 at 06:15 UTC 0fe5043dad74352c08447eb1913df0b6c3f2731c
3 files changed +290
Makefile
+1
@@ -722,6 +722,7 @@ LIB_OBJS += diff-lib.o
722 LIB_OBJS += diff-no-index.o
723 LIB_OBJS += diff.o
724 LIB_OBJS += dir.o
725 +LIB_OBJS += dir-iterator.o
726 LIB_OBJS += editor.o
727 LIB_OBJS += entry.o
728 LIB_OBJS += environment.o
dir-iterator.c new
+202
@@ -0,0 +1,202 @@
1 +#include "cache.h"
2 +#include "dir.h"
3 +#include "iterator.h"
4 +#include "dir-iterator.h"
5 +
6 +struct dir_iterator_level {
7 + int initialized;
8 +
9 + DIR *dir;
10 +
11 + /*
12 + * The length of the directory part of path at this level
13 + * (including a trailing '/'):
14 + */
15 + size_t prefix_len;
16 +
17 + /*
18 + * The last action that has been taken with the current entry
19 + * (needed for directories, which have to be included in the
20 + * iteration and also iterated into):
21 + */
22 + enum {
23 + DIR_STATE_ITER,
24 + DIR_STATE_RECURSE
25 + } dir_state;
26 +};
27 +
28 +/*
29 + * The full data structure used to manage the internal directory
30 + * iteration state. It includes members that are not part of the
31 + * public interface.
32 + */
33 +struct dir_iterator_int {
34 + struct dir_iterator base;
35 +
36 + /*
37 + * The number of levels currently on the stack. This is always
38 + * at least 1, because when it becomes zero the iteration is
39 + * ended and this struct is freed.
40 + */
41 + size_t levels_nr;
42 +
43 + /* The number of levels that have been allocated on the stack */
44 + size_t levels_alloc;
45 +
46 + /*
47 + * A stack of levels. levels[0] is the uppermost directory
48 + * that will be included in this iteration.
49 + */
50 + struct dir_iterator_level *levels;
51 +};
52 +
53 +int dir_iterator_advance(struct dir_iterator *dir_iterator)
54 +{
55 + struct dir_iterator_int *iter =
56 + (struct dir_iterator_int *)dir_iterator;
57 +
58 + while (1) {
59 + struct dir_iterator_level *level =
60 + &iter->levels[iter->levels_nr - 1];
61 + struct dirent *de;
62 +
63 + if (!level->initialized) {
64 + /*
65 + * Note: dir_iterator_begin() ensures that
66 + * path is not the empty string.
67 + */
68 + if (!is_dir_sep(iter->base.path.buf[iter->base.path.len - 1]))
69 + strbuf_addch(&iter->base.path, '/');
70 + level->prefix_len = iter->base.path.len;
71 +
72 + level->dir = opendir(iter->base.path.buf);
73 + if (!level->dir && errno != ENOENT) {
74 + warning("error opening directory %s: %s",
75 + iter->base.path.buf, strerror(errno));
76 + /* Popping the level is handled below */
77 + }
78 +
79 + level->initialized = 1;
80 + } else if (S_ISDIR(iter->base.st.st_mode)) {
81 + if (level->dir_state == DIR_STATE_ITER) {
82 + /*
83 + * The directory was just iterated
84 + * over; now prepare to iterate into
85 + * it.
86 + */
87 + level->dir_state = DIR_STATE_RECURSE;
88 + ALLOC_GROW(iter->levels, iter->levels_nr + 1,
89 + iter->levels_alloc);
90 + level = &iter->levels[iter->levels_nr++];
91 + level->initialized = 0;
92 + continue;
93 + } else {
94 + /*
95 + * The directory has already been
96 + * iterated over and iterated into;
97 + * we're done with it.
98 + */
99 + }
100 + }
101 +
102 + if (!level->dir) {
103 + /*
104 + * This level is exhausted (or wasn't opened
105 + * successfully); pop up a level.
106 + */
107 + if (--iter->levels_nr == 0)
108 + return dir_iterator_abort(dir_iterator);
109 +
110 + continue;
111 + }
112 +
113 + /*
114 + * Loop until we find an entry that we can give back
115 + * to the caller:
116 + */
117 + while (1) {
118 + strbuf_setlen(&iter->base.path, level->prefix_len);
119 + errno = 0;
120 + de = readdir(level->dir);
121 +
122 + if (!de) {
123 + /* This level is exhausted; pop up a level. */
124 + if (errno) {
125 + warning("error reading directory %s: %s",
126 + iter->base.path.buf, strerror(errno));
127 + } else if (closedir(level->dir))
128 + warning("error closing directory %s: %s",
129 + iter->base.path.buf, strerror(errno));
130 +
131 + level->dir = NULL;
132 + if (--iter->levels_nr == 0)
133 + return dir_iterator_abort(dir_iterator);
134 + break;
135 + }
136 +
137 + if (is_dot_or_dotdot(de->d_name))
138 + continue;
139 +
140 + strbuf_addstr(&iter->base.path, de->d_name);
141 + if (lstat(iter->base.path.buf, &iter->base.st) < 0) {
142 + if (errno != ENOENT)
143 + warning("error reading path '%s': %s",
144 + iter->base.path.buf,
145 + strerror(errno));
146 + continue;
147 + }
148 +
149 + /*
150 + * We have to set these each time because
151 + * the path strbuf might have been realloc()ed.
152 + */
153 + iter->base.relative_path =
154 + iter->base.path.buf + iter->levels[0].prefix_len;
155 + iter->base.basename =
156 + iter->base.path.buf + level->prefix_len;
157 + level->dir_state = DIR_STATE_ITER;
158 +
159 + return ITER_OK;
160 + }
161 + }
162 +}
163 +
164 +int dir_iterator_abort(struct dir_iterator *dir_iterator)
165 +{
166 + struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator;
167 +
168 + for (; iter->levels_nr; iter->levels_nr--) {
169 + struct dir_iterator_level *level =
170 + &iter->levels[iter->levels_nr - 1];
171 +
172 + if (level->dir && closedir(level->dir)) {
173 + strbuf_setlen(&iter->base.path, level->prefix_len);
174 + warning("error closing directory %s: %s",
175 + iter->base.path.buf, strerror(errno));
176 + }
177 + }
178 +
179 + free(iter->levels);
180 + strbuf_release(&iter->base.path);
181 + free(iter);
182 + return ITER_DONE;
183 +}
184 +
185 +struct dir_iterator *dir_iterator_begin(const char *path)
186 +{
187 + struct dir_iterator_int *iter = xcalloc(1, sizeof(*iter));
188 + struct dir_iterator *dir_iterator = &iter->base;
189 +
190 + if (!path || !*path)
191 + die("BUG: empty path passed to dir_iterator_begin()");
192 +
193 + strbuf_init(&iter->base.path, PATH_MAX);
194 + strbuf_addstr(&iter->base.path, path);
195 +
196 + ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
197 +
198 + iter->levels_nr = 1;
199 + iter->levels[0].initialized = 0;
200 +
201 + return dir_iterator;
202 +}
dir-iterator.h new
+87
@@ -0,0 +1,87 @@
1 +#ifndef DIR_ITERATOR_H
2 +#define DIR_ITERATOR_H
3 +
4 +/*
5 + * Iterate over a directory tree.
6 + *
7 + * Iterate over a directory tree, recursively, including paths of all
8 + * types and hidden paths. Skip "." and ".." entries and don't follow
9 + * symlinks except for the original path.
10 + *
11 + * Every time dir_iterator_advance() is called, update the members of
12 + * the dir_iterator structure to reflect the next path in the
13 + * iteration. The order that paths are iterated over within a
14 + * directory is undefined, but directory paths are always iterated
15 + * over before the subdirectory contents.
16 + *
17 + * A typical iteration looks like this:
18 + *
19 + * int ok;
20 + * struct iterator *iter = dir_iterator_begin(path);
21 + *
22 + * while ((ok = dir_iterator_advance(iter)) == ITER_OK) {
23 + * if (want_to_stop_iteration()) {
24 + * ok = dir_iterator_abort(iter);
25 + * break;
26 + * }
27 + *
28 + * // Access information about the current path:
29 + * if (S_ISDIR(iter->st.st_mode))
30 + * printf("%s is a directory\n", iter->relative_path);
31 + * }
32 + *
33 + * if (ok != ITER_DONE)
34 + * handle_error();
35 + *
36 + * Callers are allowed to modify iter->path while they are working,
37 + * but they must restore it to its original contents before calling
38 + * dir_iterator_advance() again.
39 + */
40 +
41 +struct dir_iterator {
42 + /* The current path: */
43 + struct strbuf path;
44 +
45 + /*
46 + * The current path relative to the starting path. This part
47 + * of the path always uses "/" characters to separate path
48 + * components:
49 + */
50 + const char *relative_path;
51 +
52 + /* The current basename: */
53 + const char *basename;
54 +
55 + /* The result of calling lstat() on path: */
56 + struct stat st;
57 +};
58 +
59 +/*
60 + * Start a directory iteration over path. Return a dir_iterator that
61 + * holds the internal state of the iteration.
62 + *
63 + * The iteration includes all paths under path, not including path
64 + * itself and not including "." or ".." entries.
65 + *
66 + * path is the starting directory. An internal copy will be made.
67 + */
68 +struct dir_iterator *dir_iterator_begin(const char *path);
69 +
70 +/*
71 + * Advance the iterator to the first or next item and return ITER_OK.
72 + * If the iteration is exhausted, free the dir_iterator and any
73 + * resources associated with it and return ITER_DONE. On error, free
74 + * dir_iterator and associated resources and return ITER_ERROR. It is
75 + * a bug to use iterator or call this function again after it has
76 + * returned ITER_DONE or ITER_ERROR.
77 + */
78 +int dir_iterator_advance(struct dir_iterator *iterator);
79 +
80 +/*
81 + * End the iteration before it has been exhausted. Free the
82 + * dir_iterator and any associated resources and return ITER_DONE. On
83 + * error, free the dir_iterator and return ITER_ERROR.
84 + */
85 +int dir_iterator_abort(struct dir_iterator *iterator);
86 +
87 +#endif