dir.con commit Merge branch 'eb/quilt' into next (7723522)
   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
  14void 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
  30static 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
  74void 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
  81static 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
  95static 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 */
 106static 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
 153int 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
 169static 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
 188static 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 */
 208static 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
 281static 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
 290int 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}