Clean up git-ls-file directory walking library interface
[git.git] / dir.c
1 /*
2  * This handles recursive filename detection with exclude
3  * files, index knowledge etc..
4  *
5  * Copyright (C) Linus Torvalds, 2005-2006
6  *               Junio Hamano, 2005-2006
7  */
8 #include <dirent.h>
9 #include <fnmatch.h>
10
11 #include "cache.h"
12 #include "dir.h"
13
14 void add_exclude(const char *string, const char *base,
15                  int baselen, struct exclude_list *which)
16 {
17         struct exclude *x = xmalloc(sizeof (*x));
18
19         x->pattern = string;
20         x->base = base;
21         x->baselen = baselen;
22         if (which->nr == which->alloc) {
23                 which->alloc = alloc_nr(which->alloc);
24                 which->excludes = realloc(which->excludes,
25                                           which->alloc * sizeof(x));
26         }
27         which->excludes[which->nr++] = x;
28 }
29
30 static int add_excludes_from_file_1(const char *fname,
31                                     const char *base,
32                                     int baselen,
33                                     struct exclude_list *which)
34 {
35         int fd, i;
36         long size;
37         char *buf, *entry;
38
39         fd = open(fname, O_RDONLY);
40         if (fd < 0)
41                 goto err;
42         size = lseek(fd, 0, SEEK_END);
43         if (size < 0)
44                 goto err;
45         lseek(fd, 0, SEEK_SET);
46         if (size == 0) {
47                 close(fd);
48                 return 0;
49         }
50         buf = xmalloc(size+1);
51         if (read(fd, buf, size) != size)
52                 goto err;
53         close(fd);
54
55         buf[size++] = '\n';
56         entry = buf;
57         for (i = 0; i < size; i++) {
58                 if (buf[i] == '\n') {
59                         if (entry != buf + i && entry[0] != '#') {
60                                 buf[i - (i && buf[i-1] == '\r')] = 0;
61                                 add_exclude(entry, base, baselen, which);
62                         }
63                         entry = buf + i + 1;
64                 }
65         }
66         return 0;
67
68  err:
69         if (0 <= fd)
70                 close(fd);
71         return -1;
72 }
73
74 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
75 {
76         if (add_excludes_from_file_1(fname, "", 0,
77                                      &dir->exclude_list[EXC_FILE]) < 0)
78                 die("cannot use %s as an exclude file", fname);
79 }
80
81 static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
82 {
83         char exclude_file[PATH_MAX];
84         struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
85         int current_nr = el->nr;
86
87         if (dir->exclude_per_dir) {
88                 memcpy(exclude_file, base, baselen);
89                 strcpy(exclude_file + baselen, dir->exclude_per_dir);
90                 add_excludes_from_file_1(exclude_file, base, baselen, el);
91         }
92         return current_nr;
93 }
94
95 static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
96 {
97         struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
98
99         while (stk < el->nr)
100                 free(el->excludes[--el->nr]);
101 }
102
103 /* Scan the list and let the last match determines the fate.
104  * Return 1 for exclude, 0 for include and -1 for undecided.
105  */
106 static int excluded_1(const char *pathname,
107                       int pathlen,
108                       struct exclude_list *el)
109 {
110         int i;
111
112         if (el->nr) {
113                 for (i = el->nr - 1; 0 <= i; i--) {
114                         struct exclude *x = el->excludes[i];
115                         const char *exclude = x->pattern;
116                         int to_exclude = 1;
117
118                         if (*exclude == '!') {
119                                 to_exclude = 0;
120                                 exclude++;
121                         }
122
123                         if (!strchr(exclude, '/')) {
124                                 /* match basename */
125                                 const char *basename = strrchr(pathname, '/');
126                                 basename = (basename) ? basename+1 : pathname;
127                                 if (fnmatch(exclude, basename, 0) == 0)
128                                         return to_exclude;
129                         }
130                         else {
131                                 /* match with FNM_PATHNAME:
132                                  * exclude has base (baselen long) implicitly
133                                  * in front of it.
134                                  */
135                                 int baselen = x->baselen;
136                                 if (*exclude == '/')
137                                         exclude++;
138
139                                 if (pathlen < baselen ||
140                                     (baselen && pathname[baselen-1] != '/') ||
141                                     strncmp(pathname, x->base, baselen))
142                                     continue;
143
144                                 if (fnmatch(exclude, pathname+baselen,
145                                             FNM_PATHNAME) == 0)
146                                         return to_exclude;
147                         }
148                 }
149         }
150         return -1; /* undecided */
151 }
152
153 int excluded(struct dir_struct *dir, const char *pathname)
154 {
155         int pathlen = strlen(pathname);
156         int st;
157
158         for (st = EXC_CMDL; st <= EXC_FILE; st++) {
159                 switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
160                 case 0:
161                         return 0;
162                 case 1:
163                         return 1;
164                 }
165         }
166         return 0;
167 }
168
169 static void add_name(struct dir_struct *dir, const char *pathname, int len)
170 {
171         struct dir_entry *ent;
172
173         if (cache_name_pos(pathname, len) >= 0)
174                 return;
175
176         if (dir->nr == dir->alloc) {
177                 int alloc = alloc_nr(dir->alloc);
178                 dir->alloc = alloc;
179                 dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
180         }
181         ent = xmalloc(sizeof(*ent) + len + 1);
182         ent->len = len;
183         memcpy(ent->name, pathname, len);
184         ent->name[len] = 0;
185         dir->entries[dir->nr++] = ent;
186 }
187
188 static int dir_exists(const char *dirname, int len)
189 {
190         int pos = cache_name_pos(dirname, len);
191         if (pos >= 0)
192                 return 1;
193         pos = -pos-1;
194         if (pos >= active_nr) /* can't */
195                 return 0;
196         return !strncmp(active_cache[pos]->name, dirname, len);
197 }
198
199 /*
200  * Read a directory tree. We currently ignore anything but
201  * directories, regular files and symlinks. That's because git
202  * doesn't handle them at all yet. Maybe that will change some
203  * day.
204  *
205  * Also, we ignore the name ".git" (even if it is not a directory).
206  * That likely will not change.
207  */
208 static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
209 {
210         DIR *fdir = opendir(path);
211         int contents = 0;
212
213         if (fdir) {
214                 int exclude_stk;
215                 struct dirent *de;
216                 char fullname[MAXPATHLEN + 1];
217                 memcpy(fullname, base, baselen);
218
219                 exclude_stk = push_exclude_per_directory(dir, base, baselen);
220
221                 while ((de = readdir(fdir)) != NULL) {
222                         int len;
223
224                         if ((de->d_name[0] == '.') &&
225                             (de->d_name[1] == 0 ||
226                              !strcmp(de->d_name + 1, ".") ||
227                              !strcmp(de->d_name + 1, "git")))
228                                 continue;
229                         len = strlen(de->d_name);
230                         memcpy(fullname + baselen, de->d_name, len+1);
231                         if (excluded(dir, fullname) != dir->show_ignored) {
232                                 if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
233                                         continue;
234                                 }
235                         }
236
237                         switch (DTYPE(de)) {
238                         struct stat st;
239                         int subdir, rewind_base;
240                         default:
241                                 continue;
242                         case DT_UNKNOWN:
243                                 if (lstat(fullname, &st))
244                                         continue;
245                                 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
246                                         break;
247                                 if (!S_ISDIR(st.st_mode))
248                                         continue;
249                                 /* fallthrough */
250                         case DT_DIR:
251                                 memcpy(fullname + baselen + len, "/", 2);
252                                 len++;
253                                 rewind_base = dir->nr;
254                                 subdir = read_directory_recursive(dir, fullname, fullname,
255                                                         baselen + len);
256                                 if (dir->show_other_directories &&
257                                     (subdir || !dir->hide_empty_directories) &&
258                                     !dir_exists(fullname, baselen + len)) {
259                                         // Rewind the read subdirectory
260                                         while (dir->nr > rewind_base)
261                                                 free(dir->entries[--dir->nr]);
262                                         break;
263                                 }
264                                 contents += subdir;
265                                 continue;
266                         case DT_REG:
267                         case DT_LNK:
268                                 break;
269                         }
270                         add_name(dir, fullname, baselen + len);
271                         contents++;
272                 }
273                 closedir(fdir);
274
275                 pop_exclude_per_directory(dir, exclude_stk);
276         }
277
278         return contents;
279 }
280
281 static int cmp_name(const void *p1, const void *p2)
282 {
283         const struct dir_entry *e1 = *(const struct dir_entry **)p1;
284         const struct dir_entry *e2 = *(const struct dir_entry **)p2;
285
286         return cache_name_compare(e1->name, e1->len,
287                                   e2->name, e2->len);
288 }
289
290 int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
291 {
292         /*
293          * Make sure to do the per-directory exclude for all the
294          * directories leading up to our base.
295          */
296         if (baselen) {
297                 if (dir->exclude_per_dir) {
298                         char *p, *pp = xmalloc(baselen+1);
299                         memcpy(pp, base, baselen+1);
300                         p = pp;
301                         while (1) {
302                                 char save = *p;
303                                 *p = 0;
304                                 push_exclude_per_directory(dir, pp, p-pp);
305                                 *p++ = save;
306                                 if (!save)
307                                         break;
308                                 p = strchr(p, '/');
309                                 if (p)
310                                         p++;
311                                 else
312                                         p = pp + baselen;
313                         }
314                         free(pp);
315                 }
316         }
317
318         read_directory_recursive(dir, path, base, baselen);
319         qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
320         return dir->nr;
321 }