runstatus: do not recurse into subdirectories if not needed
[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 int common_prefix(const char **pathspec)
15 {
16         const char *path, *slash, *next;
17         int prefix;
18
19         if (!pathspec)
20                 return 0;
21
22         path = *pathspec;
23         slash = strrchr(path, '/');
24         if (!slash)
25                 return 0;
26
27         prefix = slash - path + 1;
28         while ((next = *++pathspec) != NULL) {
29                 int len = strlen(next);
30                 if (len >= prefix && !memcmp(path, next, len))
31                         continue;
32                 for (;;) {
33                         if (!len)
34                                 return 0;
35                         if (next[--len] != '/')
36                                 continue;
37                         if (memcmp(path, next, len+1))
38                                 continue;
39                         prefix = len + 1;
40                         break;
41                 }
42         }
43         return prefix;
44 }
45
46 static int match_one(const char *match, const char *name, int namelen)
47 {
48         int matchlen;
49
50         /* If the match was just the prefix, we matched */
51         matchlen = strlen(match);
52         if (!matchlen)
53                 return 1;
54
55         /*
56          * If we don't match the matchstring exactly,
57          * we need to match by fnmatch
58          */
59         if (strncmp(match, name, matchlen))
60                 return !fnmatch(match, name, 0);
61
62         /*
63          * If we did match the string exactly, we still
64          * need to make sure that it happened on a path
65          * component boundary (ie either the last character
66          * of the match was '/', or the next character of
67          * the name was '/' or the terminating NUL.
68          */
69         return  match[matchlen-1] == '/' ||
70                 name[matchlen] == '/' ||
71                 !name[matchlen];
72 }
73
74 int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
75 {
76         int retval;
77         const char *match;
78
79         name += prefix;
80         namelen -= prefix;
81
82         for (retval = 0; (match = *pathspec++) != NULL; seen++) {
83                 if (retval & *seen)
84                         continue;
85                 match += prefix;
86                 if (match_one(match, name, namelen)) {
87                         retval = 1;
88                         *seen = 1;
89                 }
90         }
91         return retval;
92 }
93
94 void add_exclude(const char *string, const char *base,
95                  int baselen, struct exclude_list *which)
96 {
97         struct exclude *x = xmalloc(sizeof (*x));
98
99         x->pattern = string;
100         x->base = base;
101         x->baselen = baselen;
102         if (which->nr == which->alloc) {
103                 which->alloc = alloc_nr(which->alloc);
104                 which->excludes = xrealloc(which->excludes,
105                                            which->alloc * sizeof(x));
106         }
107         which->excludes[which->nr++] = x;
108 }
109
110 static int add_excludes_from_file_1(const char *fname,
111                                     const char *base,
112                                     int baselen,
113                                     struct exclude_list *which)
114 {
115         struct stat st;
116         int fd, i;
117         long size;
118         char *buf, *entry;
119
120         fd = open(fname, O_RDONLY);
121         if (fd < 0 || fstat(fd, &st) < 0)
122                 goto err;
123         size = st.st_size;
124         if (size == 0) {
125                 close(fd);
126                 return 0;
127         }
128         buf = xmalloc(size+1);
129         if (read(fd, buf, size) != size)
130                 goto err;
131         close(fd);
132
133         buf[size++] = '\n';
134         entry = buf;
135         for (i = 0; i < size; i++) {
136                 if (buf[i] == '\n') {
137                         if (entry != buf + i && entry[0] != '#') {
138                                 buf[i - (i && buf[i-1] == '\r')] = 0;
139                                 add_exclude(entry, base, baselen, which);
140                         }
141                         entry = buf + i + 1;
142                 }
143         }
144         return 0;
145
146  err:
147         if (0 <= fd)
148                 close(fd);
149         return -1;
150 }
151
152 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
153 {
154         if (add_excludes_from_file_1(fname, "", 0,
155                                      &dir->exclude_list[EXC_FILE]) < 0)
156                 die("cannot use %s as an exclude file", fname);
157 }
158
159 static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
160 {
161         char exclude_file[PATH_MAX];
162         struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
163         int current_nr = el->nr;
164
165         if (dir->exclude_per_dir) {
166                 memcpy(exclude_file, base, baselen);
167                 strcpy(exclude_file + baselen, dir->exclude_per_dir);
168                 add_excludes_from_file_1(exclude_file, base, baselen, el);
169         }
170         return current_nr;
171 }
172
173 static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
174 {
175         struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
176
177         while (stk < el->nr)
178                 free(el->excludes[--el->nr]);
179 }
180
181 /* Scan the list and let the last match determines the fate.
182  * Return 1 for exclude, 0 for include and -1 for undecided.
183  */
184 static int excluded_1(const char *pathname,
185                       int pathlen,
186                       struct exclude_list *el)
187 {
188         int i;
189
190         if (el->nr) {
191                 for (i = el->nr - 1; 0 <= i; i--) {
192                         struct exclude *x = el->excludes[i];
193                         const char *exclude = x->pattern;
194                         int to_exclude = 1;
195
196                         if (*exclude == '!') {
197                                 to_exclude = 0;
198                                 exclude++;
199                         }
200
201                         if (!strchr(exclude, '/')) {
202                                 /* match basename */
203                                 const char *basename = strrchr(pathname, '/');
204                                 basename = (basename) ? basename+1 : pathname;
205                                 if (fnmatch(exclude, basename, 0) == 0)
206                                         return to_exclude;
207                         }
208                         else {
209                                 /* match with FNM_PATHNAME:
210                                  * exclude has base (baselen long) implicitly
211                                  * in front of it.
212                                  */
213                                 int baselen = x->baselen;
214                                 if (*exclude == '/')
215                                         exclude++;
216
217                                 if (pathlen < baselen ||
218                                     (baselen && pathname[baselen-1] != '/') ||
219                                     strncmp(pathname, x->base, baselen))
220                                     continue;
221
222                                 if (fnmatch(exclude, pathname+baselen,
223                                             FNM_PATHNAME) == 0)
224                                         return to_exclude;
225                         }
226                 }
227         }
228         return -1; /* undecided */
229 }
230
231 int excluded(struct dir_struct *dir, const char *pathname)
232 {
233         int pathlen = strlen(pathname);
234         int st;
235
236         for (st = EXC_CMDL; st <= EXC_FILE; st++) {
237                 switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
238                 case 0:
239                         return 0;
240                 case 1:
241                         return 1;
242                 }
243         }
244         return 0;
245 }
246
247 static void add_name(struct dir_struct *dir, const char *pathname, int len)
248 {
249         struct dir_entry *ent;
250
251         if (cache_name_pos(pathname, len) >= 0)
252                 return;
253
254         if (dir->nr == dir->alloc) {
255                 int alloc = alloc_nr(dir->alloc);
256                 dir->alloc = alloc;
257                 dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
258         }
259         ent = xmalloc(sizeof(*ent) + len + 1);
260         ent->len = len;
261         memcpy(ent->name, pathname, len);
262         ent->name[len] = 0;
263         dir->entries[dir->nr++] = ent;
264 }
265
266 static int dir_exists(const char *dirname, int len)
267 {
268         int pos = cache_name_pos(dirname, len);
269         if (pos >= 0)
270                 return 1;
271         pos = -pos-1;
272         if (pos >= active_nr) /* can't */
273                 return 0;
274         return !strncmp(active_cache[pos]->name, dirname, len);
275 }
276
277 /*
278  * Read a directory tree. We currently ignore anything but
279  * directories, regular files and symlinks. That's because git
280  * doesn't handle them at all yet. Maybe that will change some
281  * day.
282  *
283  * Also, we ignore the name ".git" (even if it is not a directory).
284  * That likely will not change.
285  */
286 static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only)
287 {
288         DIR *fdir = opendir(path);
289         int contents = 0;
290
291         if (fdir) {
292                 int exclude_stk;
293                 struct dirent *de;
294                 char fullname[PATH_MAX + 1];
295                 memcpy(fullname, base, baselen);
296
297                 exclude_stk = push_exclude_per_directory(dir, base, baselen);
298
299                 while ((de = readdir(fdir)) != NULL) {
300                         int len;
301
302                         if ((de->d_name[0] == '.') &&
303                             (de->d_name[1] == 0 ||
304                              !strcmp(de->d_name + 1, ".") ||
305                              !strcmp(de->d_name + 1, "git")))
306                                 continue;
307                         len = strlen(de->d_name);
308                         memcpy(fullname + baselen, de->d_name, len+1);
309                         if (excluded(dir, fullname) != dir->show_ignored) {
310                                 if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
311                                         continue;
312                                 }
313                         }
314
315                         switch (DTYPE(de)) {
316                         struct stat st;
317                         default:
318                                 continue;
319                         case DT_UNKNOWN:
320                                 if (lstat(fullname, &st))
321                                         continue;
322                                 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
323                                         break;
324                                 if (!S_ISDIR(st.st_mode))
325                                         continue;
326                                 /* fallthrough */
327                         case DT_DIR:
328                                 memcpy(fullname + baselen + len, "/", 2);
329                                 len++;
330                                 if (dir->show_other_directories &&
331                                     !dir_exists(fullname, baselen + len)) {
332                                         if (dir->hide_empty_directories &&
333                                             !read_directory_recursive(dir,
334                                                     fullname, fullname,
335                                                     baselen + len, 1))
336                                                 continue;
337                                         break;
338                                 }
339
340                                 contents += read_directory_recursive(dir,
341                                         fullname, fullname, baselen + len, 0);
342                                 continue;
343                         case DT_REG:
344                         case DT_LNK:
345                                 break;
346                         }
347                         contents++;
348                         if (check_only)
349                                 goto exit_early;
350                         else
351                                 add_name(dir, fullname, baselen + len);
352                 }
353 exit_early:
354                 closedir(fdir);
355
356                 pop_exclude_per_directory(dir, exclude_stk);
357         }
358
359         return contents;
360 }
361
362 static int cmp_name(const void *p1, const void *p2)
363 {
364         const struct dir_entry *e1 = *(const struct dir_entry **)p1;
365         const struct dir_entry *e2 = *(const struct dir_entry **)p2;
366
367         return cache_name_compare(e1->name, e1->len,
368                                   e2->name, e2->len);
369 }
370
371 int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
372 {
373         /*
374          * Make sure to do the per-directory exclude for all the
375          * directories leading up to our base.
376          */
377         if (baselen) {
378                 if (dir->exclude_per_dir) {
379                         char *p, *pp = xmalloc(baselen+1);
380                         memcpy(pp, base, baselen+1);
381                         p = pp;
382                         while (1) {
383                                 char save = *p;
384                                 *p = 0;
385                                 push_exclude_per_directory(dir, pp, p-pp);
386                                 *p++ = save;
387                                 if (!save)
388                                         break;
389                                 p = strchr(p, '/');
390                                 if (p)
391                                         p++;
392                                 else
393                                         p = pp + baselen;
394                         }
395                         free(pp);
396                 }
397         }
398
399         read_directory_recursive(dir, path, base, baselen, 0);
400         qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
401         return dir->nr;
402 }
403
404 int
405 file_exists(const char *f)
406 {
407   struct stat sb;
408   return stat(f, &sb) == 0;
409 }