ls-files.con commit [PATCH 2/3] Support symlinks in git-ls-files --others. (a15c1c6)
   1/*
   2 * This merges the file listing in the directory cache index
   3 * with the actual working directory list, and shows different
   4 * combinations of the two.
   5 *
   6 * Copyright (C) Linus Torvalds, 2005
   7 */
   8#include <dirent.h>
   9#include <fnmatch.h>
  10
  11#include "cache.h"
  12
  13static int show_deleted = 0;
  14static int show_cached = 0;
  15static int show_others = 0;
  16static int show_ignored = 0;
  17static int show_stage = 0;
  18static int show_unmerged = 0;
  19static int line_terminator = '\n';
  20
  21static const char *tag_cached = "";
  22static const char *tag_unmerged = "";
  23static const char *tag_removed = "";
  24static const char *tag_other = "";
  25
  26static int nr_excludes;
  27static const char **excludes;
  28static int excludes_alloc;
  29
  30static void add_exclude(const char *string)
  31{
  32        if (nr_excludes == excludes_alloc) {
  33                excludes_alloc = alloc_nr(excludes_alloc);
  34                excludes = realloc(excludes, excludes_alloc*sizeof(char *));
  35        }
  36        excludes[nr_excludes++] = string;
  37}
  38
  39static void add_excludes_from_file(const char *fname)
  40{
  41        int fd, i;
  42        long size;
  43        char *buf, *entry;
  44
  45        fd = open(fname, O_RDONLY);
  46        if (fd < 0)
  47                goto err;
  48        size = lseek(fd, 0, SEEK_END);
  49        if (size < 0)
  50                goto err;
  51        lseek(fd, 0, SEEK_SET);
  52        if (size == 0) {
  53                close(fd);
  54                return;
  55        }
  56        buf = xmalloc(size);
  57        if (read(fd, buf, size) != size)
  58                goto err;
  59        close(fd);
  60
  61        entry = buf;
  62        for (i = 0; i < size; i++) {
  63                if (buf[i] == '\n') {
  64                        if (entry != buf + i) {
  65                                buf[i] = 0;
  66                                add_exclude(entry);
  67                        }
  68                        entry = buf + i + 1;
  69                }
  70        }
  71        return;
  72
  73err:    perror(fname);
  74        exit(1);
  75}
  76
  77static int excluded(const char *pathname)
  78{
  79        int i;
  80        if (nr_excludes) {
  81                const char *basename = strrchr(pathname, '/');
  82                basename = (basename) ? basename+1 : pathname;
  83                for (i = 0; i < nr_excludes; i++)
  84                        if (fnmatch(excludes[i], basename, 0) == 0)
  85                                return 1;
  86        }
  87        return 0;
  88}
  89
  90static const char **dir;
  91static int nr_dir;
  92static int dir_alloc;
  93
  94static void add_name(const char *pathname, int len)
  95{
  96        char *name;
  97
  98        if (cache_name_pos(pathname, len) >= 0)
  99                return;
 100
 101        if (nr_dir == dir_alloc) {
 102                dir_alloc = alloc_nr(dir_alloc);
 103                dir = xrealloc(dir, dir_alloc*sizeof(char *));
 104        }
 105        name = xmalloc(len + 1);
 106        memcpy(name, pathname, len + 1);
 107        dir[nr_dir++] = name;
 108}
 109
 110/*
 111 * Read a directory tree. We currently ignore anything but
 112 * directories, regular files and symlinks. That's because git
 113 * doesn't handle them at all yet. Maybe that will change some
 114 * day.
 115 *
 116 * Also, we currently ignore all names starting with a dot.
 117 * That likely will not change.
 118 */
 119static void read_directory(const char *path, const char *base, int baselen)
 120{
 121        DIR *dir = opendir(path);
 122
 123        if (dir) {
 124                struct dirent *de;
 125                char fullname[MAXPATHLEN + 1];
 126                memcpy(fullname, base, baselen);
 127
 128                while ((de = readdir(dir)) != NULL) {
 129                        int len;
 130
 131                        if (de->d_name[0] == '.')
 132                                continue;
 133                        if (excluded(de->d_name) != show_ignored)
 134                                continue;
 135                        len = strlen(de->d_name);
 136                        memcpy(fullname + baselen, de->d_name, len+1);
 137
 138                        switch (DTYPE(de)) {
 139                        struct stat st;
 140                        default:
 141                                continue;
 142                        case DT_UNKNOWN:
 143                                if (lstat(fullname, &st))
 144                                        continue;
 145                                if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
 146                                        break;
 147                                if (!S_ISDIR(st.st_mode))
 148                                        continue;
 149                                /* fallthrough */
 150                        case DT_DIR:
 151                                memcpy(fullname + baselen + len, "/", 2);
 152                                read_directory(fullname, fullname,
 153                                               baselen + len + 1);
 154                                continue;
 155                        case DT_REG:
 156                        case DT_LNK:
 157                                break;
 158                        }
 159                        add_name(fullname, baselen + len);
 160                }
 161                closedir(dir);
 162        }
 163}
 164
 165static int cmp_name(const void *p1, const void *p2)
 166{
 167        const char *n1 = *(const char **)p1;
 168        const char *n2 = *(const char **)p2;
 169        int l1 = strlen(n1), l2 = strlen(n2);
 170
 171        return cache_name_compare(n1, l1, n2, l2);
 172}
 173
 174static void show_files(void)
 175{
 176        int i;
 177
 178        /* For cached/deleted files we don't need to even do the readdir */
 179        if (show_others) {
 180                read_directory(".", "", 0);
 181                qsort(dir, nr_dir, sizeof(char *), cmp_name);
 182                for (i = 0; i < nr_dir; i++)
 183                        printf("%s%s%c", tag_other, dir[i], line_terminator);
 184        }
 185        if (show_cached | show_stage) {
 186                for (i = 0; i < active_nr; i++) {
 187                        struct cache_entry *ce = active_cache[i];
 188                        if (excluded(ce->name) != show_ignored)
 189                                continue;
 190                        if (show_unmerged && !ce_stage(ce))
 191                                continue;
 192                        if (!show_stage)
 193                                printf("%s%s%c",
 194                                       ce_stage(ce) ? tag_unmerged :
 195                                       tag_cached,
 196                                       ce->name, line_terminator);
 197                        else
 198                                printf("%s%06o %s %d %s%c",
 199                                       ce_stage(ce) ? tag_unmerged :
 200                                       tag_cached,
 201                                       ntohl(ce->ce_mode),
 202                                       sha1_to_hex(ce->sha1),
 203                                       ce_stage(ce),
 204                                       ce->name, line_terminator); 
 205                }
 206        }
 207        if (show_deleted) {
 208                for (i = 0; i < active_nr; i++) {
 209                        struct cache_entry *ce = active_cache[i];
 210                        struct stat st;
 211                        if (excluded(ce->name) != show_ignored)
 212                                continue;
 213                        if (!lstat(ce->name, &st))
 214                                continue;
 215                        printf("%s%s%c", tag_removed, ce->name,
 216                               line_terminator);
 217                }
 218        }
 219}
 220
 221static const char *ls_files_usage =
 222        "ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged])* "
 223        "[ --ignored [--exclude=<pattern>] [--exclude-from=<file>) ]";
 224
 225int main(int argc, char **argv)
 226{
 227        int i;
 228
 229        for (i = 1; i < argc; i++) {
 230                char *arg = argv[i];
 231
 232                if (!strcmp(arg, "-z")) {
 233                        line_terminator = 0;
 234                } else if (!strcmp(arg, "-t")) {
 235                        tag_cached = "H ";
 236                        tag_unmerged = "M ";
 237                        tag_removed = "R ";
 238                        tag_other = "? ";
 239                } else if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
 240                        show_cached = 1;
 241                } else if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
 242                        show_deleted = 1;
 243                } else if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
 244                        show_others = 1;
 245                } else if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
 246                        show_ignored = 1;
 247                } else if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
 248                        show_stage = 1;
 249                } else if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
 250                        /* There's no point in showing unmerged unless
 251                         * you also show the stage information.
 252                         */
 253                        show_stage = 1;
 254                        show_unmerged = 1;
 255                } else if (!strcmp(arg, "-x") && i+1 < argc) {
 256                        add_exclude(argv[++i]);
 257                } else if (!strncmp(arg, "--exclude=", 10)) {
 258                        add_exclude(arg+10);
 259                } else if (!strcmp(arg, "-X") && i+1 < argc) {
 260                        add_excludes_from_file(argv[++i]);
 261                } else if (!strncmp(arg, "--exclude-from=", 15)) {
 262                        add_excludes_from_file(arg+15);
 263                } else
 264                        usage(ls_files_usage);
 265        }
 266
 267        if (show_ignored && !nr_excludes) {
 268                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
 269                        argv[0]);
 270                exit(1);
 271        }
 272
 273        /* With no flags, we default to showing the cached files */
 274        if (!(show_stage | show_deleted | show_others | show_unmerged))
 275                show_cached = 1;
 276
 277        read_cache();
 278        show_files();
 279        return 0;
 280}