dir.con commit Patch for http-fetch.c and older curl releases (c774b2d)
   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
  14int 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
  46static 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
  74int 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
  94void 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
 110static 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
 152void 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
 159static 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
 173static 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 */
 184static 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
 231int 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
 247static 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
 266static 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 */
 286static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
 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                        int subdir, rewind_base;
 318                        default:
 319                                continue;
 320                        case DT_UNKNOWN:
 321                                if (lstat(fullname, &st))
 322                                        continue;
 323                                if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
 324                                        break;
 325                                if (!S_ISDIR(st.st_mode))
 326                                        continue;
 327                                /* fallthrough */
 328                        case DT_DIR:
 329                                memcpy(fullname + baselen + len, "/", 2);
 330                                len++;
 331                                rewind_base = dir->nr;
 332                                subdir = read_directory_recursive(dir, fullname, fullname,
 333                                                        baselen + len);
 334                                if (dir->show_other_directories &&
 335                                    (subdir || !dir->hide_empty_directories) &&
 336                                    !dir_exists(fullname, baselen + len)) {
 337                                        /* Rewind the read subdirectory */
 338                                        while (dir->nr > rewind_base)
 339                                                free(dir->entries[--dir->nr]);
 340                                        break;
 341                                }
 342                                contents += subdir;
 343                                continue;
 344                        case DT_REG:
 345                        case DT_LNK:
 346                                break;
 347                        }
 348                        add_name(dir, fullname, baselen + len);
 349                        contents++;
 350                }
 351                closedir(fdir);
 352
 353                pop_exclude_per_directory(dir, exclude_stk);
 354        }
 355
 356        return contents;
 357}
 358
 359static int cmp_name(const void *p1, const void *p2)
 360{
 361        const struct dir_entry *e1 = *(const struct dir_entry **)p1;
 362        const struct dir_entry *e2 = *(const struct dir_entry **)p2;
 363
 364        return cache_name_compare(e1->name, e1->len,
 365                                  e2->name, e2->len);
 366}
 367
 368int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
 369{
 370        /*
 371         * Make sure to do the per-directory exclude for all the
 372         * directories leading up to our base.
 373         */
 374        if (baselen) {
 375                if (dir->exclude_per_dir) {
 376                        char *p, *pp = xmalloc(baselen+1);
 377                        memcpy(pp, base, baselen+1);
 378                        p = pp;
 379                        while (1) {
 380                                char save = *p;
 381                                *p = 0;
 382                                push_exclude_per_directory(dir, pp, p-pp);
 383                                *p++ = save;
 384                                if (!save)
 385                                        break;
 386                                p = strchr(p, '/');
 387                                if (p)
 388                                        p++;
 389                                else
 390                                        p = pp + baselen;
 391                        }
 392                        free(pp);
 393                }
 394        }
 395
 396        read_directory_recursive(dir, path, base, baselen);
 397        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
 398        return dir->nr;
 399}
 400
 401int
 402file_exists(const char *f)
 403{
 404  struct stat sb;
 405  return stat(f, &sb) == 0;
 406}