dir.con commit git-svn: bugfix and optimize the 'log' command (c0d4822)
   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 = realloc(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        int fd, i;
 116        long size;
 117        char *buf, *entry;
 118
 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);
 134
 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;
 147
 148 err:
 149        if (0 <= fd)
 150                close(fd);
 151        return -1;
 152}
 153
 154void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 155{
 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);
 159}
 160
 161static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
 162{
 163        char exclude_file[PATH_MAX];
 164        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
 165        int current_nr = el->nr;
 166
 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;
 173}
 174
 175static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
 176{
 177        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
 178
 179        while (stk < el->nr)
 180                free(el->excludes[--el->nr]);
 181}
 182
 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 */
 186static int excluded_1(const char *pathname,
 187                      int pathlen,
 188                      struct exclude_list *el)
 189{
 190        int i;
 191
 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;
 197
 198                        if (*exclude == '!') {
 199                                to_exclude = 0;
 200                                exclude++;
 201                        }
 202
 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++;
 218
 219                                if (pathlen < baselen ||
 220                                    (baselen && pathname[baselen-1] != '/') ||
 221                                    strncmp(pathname, x->base, baselen))
 222                                    continue;
 223
 224                                if (fnmatch(exclude, pathname+baselen,
 225                                            FNM_PATHNAME) == 0)
 226                                        return to_exclude;
 227                        }
 228                }
 229        }
 230        return -1; /* undecided */
 231}
 232
 233int excluded(struct dir_struct *dir, const char *pathname)
 234{
 235        int pathlen = strlen(pathname);
 236        int st;
 237
 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;
 247}
 248
 249static void add_name(struct dir_struct *dir, const char *pathname, int len)
 250{
 251        struct dir_entry *ent;
 252
 253        if (cache_name_pos(pathname, len) >= 0)
 254                return;
 255
 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;
 266}
 267
 268static int dir_exists(const char *dirname, int len)
 269{
 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);
 277}
 278
 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 */
 288static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
 289{
 290        DIR *fdir = opendir(path);
 291        int contents = 0;
 292
 293        if (fdir) {
 294                int exclude_stk;
 295                struct dirent *de;
 296                char fullname[MAXPATHLEN + 1];
 297                memcpy(fullname, base, baselen);
 298
 299                exclude_stk = push_exclude_per_directory(dir, base, baselen);
 300
 301                while ((de = readdir(fdir)) != NULL) {
 302                        int len;
 303
 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                        }
 316
 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);
 354
 355                pop_exclude_per_directory(dir, exclude_stk);
 356        }
 357
 358        return contents;
 359}
 360
 361static int cmp_name(const void *p1, const void *p2)
 362{
 363        const struct dir_entry *e1 = *(const struct dir_entry **)p1;
 364        const struct dir_entry *e2 = *(const struct dir_entry **)p2;
 365
 366        return cache_name_compare(e1->name, e1->len,
 367                                  e2->name, e2->len);
 368}
 369
 370int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
 371{
 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        }
 397
 398        read_directory_recursive(dir, path, base, baselen);
 399        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
 400        return dir->nr;
 401}