Code

gitweb: blame table row no highlight fix
[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>
11 #include "cache.h"
12 #include "dir.h"
14 int common_prefix(const char **pathspec)
15 {
16         const char *path, *slash, *next;
17         int prefix;
19         if (!pathspec)
20                 return 0;
22         path = *pathspec;
23         slash = strrchr(path, '/');
24         if (!slash)
25                 return 0;
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 }
46 static int match_one(const char *match, const char *name, int namelen)
47 {
48         int matchlen;
50         /* If the match was just the prefix, we matched */
51         matchlen = strlen(match);
52         if (!matchlen)
53                 return 1;
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);
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 }
74 int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
75 {
76         int retval;
77         const char *match;
79         name += prefix;
80         namelen -= prefix;
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 }
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));
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 = realloc(which->excludes,
105                                           which->alloc * sizeof(x));
106         }
107         which->excludes[which->nr++] = x;
110 static int add_excludes_from_file_1(const char *fname,
111                                     const char *base,
112                                     int baselen,
113                                     struct exclude_list *which)
115         int fd, i;
116         long size;
117         char *buf, *entry;
119         fd = open(fname, O_RDONLY);
120         if (fd < 0)
121                 goto err;
122         size = lseek(fd, 0, SEEK_END);
123         if (size < 0)
124                 goto err;
125         lseek(fd, 0, SEEK_SET);
126         if (size == 0) {
127                 close(fd);
128                 return 0;
129         }
130         buf = xmalloc(size+1);
131         if (read(fd, buf, size) != size)
132                 goto err;
133         close(fd);
135         buf[size++] = '\n';
136         entry = buf;
137         for (i = 0; i < size; i++) {
138                 if (buf[i] == '\n') {
139                         if (entry != buf + i && entry[0] != '#') {
140                                 buf[i - (i && buf[i-1] == '\r')] = 0;
141                                 add_exclude(entry, base, baselen, which);
142                         }
143                         entry = buf + i + 1;
144                 }
145         }
146         return 0;
148  err:
149         if (0 <= fd)
150                 close(fd);
151         return -1;
154 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
156         if (add_excludes_from_file_1(fname, "", 0,
157                                      &dir->exclude_list[EXC_FILE]) < 0)
158                 die("cannot use %s as an exclude file", fname);
161 static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
163         char exclude_file[PATH_MAX];
164         struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
165         int current_nr = el->nr;
167         if (dir->exclude_per_dir) {
168                 memcpy(exclude_file, base, baselen);
169                 strcpy(exclude_file + baselen, dir->exclude_per_dir);
170                 add_excludes_from_file_1(exclude_file, base, baselen, el);
171         }
172         return current_nr;
175 static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
177         struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
179         while (stk < el->nr)
180                 free(el->excludes[--el->nr]);
183 /* Scan the list and let the last match determines the fate.
184  * Return 1 for exclude, 0 for include and -1 for undecided.
185  */
186 static int excluded_1(const char *pathname,
187                       int pathlen,
188                       struct exclude_list *el)
190         int i;
192         if (el->nr) {
193                 for (i = el->nr - 1; 0 <= i; i--) {
194                         struct exclude *x = el->excludes[i];
195                         const char *exclude = x->pattern;
196                         int to_exclude = 1;
198                         if (*exclude == '!') {
199                                 to_exclude = 0;
200                                 exclude++;
201                         }
203                         if (!strchr(exclude, '/')) {
204                                 /* match basename */
205                                 const char *basename = strrchr(pathname, '/');
206                                 basename = (basename) ? basename+1 : pathname;
207                                 if (fnmatch(exclude, basename, 0) == 0)
208                                         return to_exclude;
209                         }
210                         else {
211                                 /* match with FNM_PATHNAME:
212                                  * exclude has base (baselen long) implicitly
213                                  * in front of it.
214                                  */
215                                 int baselen = x->baselen;
216                                 if (*exclude == '/')
217                                         exclude++;
219                                 if (pathlen < baselen ||
220                                     (baselen && pathname[baselen-1] != '/') ||
221                                     strncmp(pathname, x->base, baselen))
222                                     continue;
224                                 if (fnmatch(exclude, pathname+baselen,
225                                             FNM_PATHNAME) == 0)
226                                         return to_exclude;
227                         }
228                 }
229         }
230         return -1; /* undecided */
233 int excluded(struct dir_struct *dir, const char *pathname)
235         int pathlen = strlen(pathname);
236         int st;
238         for (st = EXC_CMDL; st <= EXC_FILE; st++) {
239                 switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
240                 case 0:
241                         return 0;
242                 case 1:
243                         return 1;
244                 }
245         }
246         return 0;
249 static void add_name(struct dir_struct *dir, const char *pathname, int len)
251         struct dir_entry *ent;
253         if (cache_name_pos(pathname, len) >= 0)
254                 return;
256         if (dir->nr == dir->alloc) {
257                 int alloc = alloc_nr(dir->alloc);
258                 dir->alloc = alloc;
259                 dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
260         }
261         ent = xmalloc(sizeof(*ent) + len + 1);
262         ent->len = len;
263         memcpy(ent->name, pathname, len);
264         ent->name[len] = 0;
265         dir->entries[dir->nr++] = ent;
268 static int dir_exists(const char *dirname, int len)
270         int pos = cache_name_pos(dirname, len);
271         if (pos >= 0)
272                 return 1;
273         pos = -pos-1;
274         if (pos >= active_nr) /* can't */
275                 return 0;
276         return !strncmp(active_cache[pos]->name, dirname, len);
279 /*
280  * Read a directory tree. We currently ignore anything but
281  * directories, regular files and symlinks. That's because git
282  * doesn't handle them at all yet. Maybe that will change some
283  * day.
284  *
285  * Also, we ignore the name ".git" (even if it is not a directory).
286  * That likely will not change.
287  */
288 static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
290         DIR *fdir = opendir(path);
291         int contents = 0;
293         if (fdir) {
294                 int exclude_stk;
295                 struct dirent *de;
296                 char fullname[MAXPATHLEN + 1];
297                 memcpy(fullname, base, baselen);
299                 exclude_stk = push_exclude_per_directory(dir, base, baselen);
301                 while ((de = readdir(fdir)) != NULL) {
302                         int len;
304                         if ((de->d_name[0] == '.') &&
305                             (de->d_name[1] == 0 ||
306                              !strcmp(de->d_name + 1, ".") ||
307                              !strcmp(de->d_name + 1, "git")))
308                                 continue;
309                         len = strlen(de->d_name);
310                         memcpy(fullname + baselen, de->d_name, len+1);
311                         if (excluded(dir, fullname) != dir->show_ignored) {
312                                 if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
313                                         continue;
314                                 }
315                         }
317                         switch (DTYPE(de)) {
318                         struct stat st;
319                         int subdir, rewind_base;
320                         default:
321                                 continue;
322                         case DT_UNKNOWN:
323                                 if (lstat(fullname, &st))
324                                         continue;
325                                 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
326                                         break;
327                                 if (!S_ISDIR(st.st_mode))
328                                         continue;
329                                 /* fallthrough */
330                         case DT_DIR:
331                                 memcpy(fullname + baselen + len, "/", 2);
332                                 len++;
333                                 rewind_base = dir->nr;
334                                 subdir = read_directory_recursive(dir, fullname, fullname,
335                                                         baselen + len);
336                                 if (dir->show_other_directories &&
337                                     (subdir || !dir->hide_empty_directories) &&
338                                     !dir_exists(fullname, baselen + len)) {
339                                         /* Rewind the read subdirectory */
340                                         while (dir->nr > rewind_base)
341                                                 free(dir->entries[--dir->nr]);
342                                         break;
343                                 }
344                                 contents += subdir;
345                                 continue;
346                         case DT_REG:
347                         case DT_LNK:
348                                 break;
349                         }
350                         add_name(dir, fullname, baselen + len);
351                         contents++;
352                 }
353                 closedir(fdir);
355                 pop_exclude_per_directory(dir, exclude_stk);
356         }
358         return contents;
361 static int cmp_name(const void *p1, const void *p2)
363         const struct dir_entry *e1 = *(const struct dir_entry **)p1;
364         const struct dir_entry *e2 = *(const struct dir_entry **)p2;
366         return cache_name_compare(e1->name, e1->len,
367                                   e2->name, e2->len);
370 int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
372         /*
373          * Make sure to do the per-directory exclude for all the
374          * directories leading up to our base.
375          */
376         if (baselen) {
377                 if (dir->exclude_per_dir) {
378                         char *p, *pp = xmalloc(baselen+1);
379                         memcpy(pp, base, baselen+1);
380                         p = pp;
381                         while (1) {
382                                 char save = *p;
383                                 *p = 0;
384                                 push_exclude_per_directory(dir, pp, p-pp);
385                                 *p++ = save;
386                                 if (!save)
387                                         break;
388                                 p = strchr(p, '/');
389                                 if (p)
390                                         p++;
391                                 else
392                                         p = pp + baselen;
393                         }
394                         free(pp);
395                 }
396         }
398         read_directory_recursive(dir, path, base, baselen);
399         qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
400         return dir->nr;