ls-files.con commit More README spelling fixes (f7ec43a)
   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 and regular files. That's because git doesn't
 113 * handle them at all yet. Maybe that will change some day.
 114 *
 115 * Also, we currently ignore all names starting with a dot.
 116 * That likely will not change.
 117 */
 118static void read_directory(const char *path, const char *base, int baselen)
 119{
 120        DIR *dir = opendir(path);
 121
 122        if (dir) {
 123                struct dirent *de;
 124                char fullname[MAXPATHLEN + 1];
 125                memcpy(fullname, base, baselen);
 126
 127                while ((de = readdir(dir)) != NULL) {
 128                        int len;
 129
 130                        if (de->d_name[0] == '.')
 131                                continue;
 132                        if (excluded(de->d_name) != show_ignored)
 133                                continue;
 134                        len = strlen(de->d_name);
 135                        memcpy(fullname + baselen, de->d_name, len+1);
 136
 137                        switch (DTYPE(de)) {
 138                        struct stat st;
 139                        default:
 140                                continue;
 141                        case DT_UNKNOWN:
 142                                if (lstat(fullname, &st))
 143                                        continue;
 144                                if (S_ISREG(st.st_mode))
 145                                        break;
 146                                if (!S_ISDIR(st.st_mode))
 147                                        continue;
 148                                /* fallthrough */
 149                        case DT_DIR:
 150                                memcpy(fullname + baselen + len, "/", 2);
 151                                read_directory(fullname, fullname,
 152                                               baselen + len + 1);
 153                                continue;
 154                        case DT_REG:
 155                                break;
 156                        }
 157                        add_name(fullname, baselen + len);
 158                }
 159                closedir(dir);
 160        }
 161}
 162
 163static int cmp_name(const void *p1, const void *p2)
 164{
 165        const char *n1 = *(const char **)p1;
 166        const char *n2 = *(const char **)p2;
 167        int l1 = strlen(n1), l2 = strlen(n2);
 168
 169        return cache_name_compare(n1, l1, n2, l2);
 170}
 171
 172static void show_files(void)
 173{
 174        int i;
 175
 176        /* For cached/deleted files we don't need to even do the readdir */
 177        if (show_others) {
 178                read_directory(".", "", 0);
 179                qsort(dir, nr_dir, sizeof(char *), cmp_name);
 180                for (i = 0; i < nr_dir; i++)
 181                        printf("%s%s%c", tag_other, dir[i], line_terminator);
 182        }
 183        if (show_cached | show_stage) {
 184                for (i = 0; i < active_nr; i++) {
 185                        struct cache_entry *ce = active_cache[i];
 186                        if (excluded(ce->name) != show_ignored)
 187                                continue;
 188                        if (show_unmerged && !ce_stage(ce))
 189                                continue;
 190                        if (!show_stage)
 191                                printf("%s%s%c",
 192                                       ce_stage(ce) ? tag_unmerged :
 193                                       tag_cached,
 194                                       ce->name, line_terminator);
 195                        else
 196                                printf("%s%06o %s %d %s%c",
 197                                       ce_stage(ce) ? tag_unmerged :
 198                                       tag_cached,
 199                                       ntohl(ce->ce_mode),
 200                                       sha1_to_hex(ce->sha1),
 201                                       ce_stage(ce),
 202                                       ce->name, line_terminator); 
 203                }
 204        }
 205        if (show_deleted) {
 206                for (i = 0; i < active_nr; i++) {
 207                        struct cache_entry *ce = active_cache[i];
 208                        struct stat st;
 209                        if (excluded(ce->name) != show_ignored)
 210                                continue;
 211                        if (!lstat(ce->name, &st))
 212                                continue;
 213                        printf("%s%s%c", tag_removed, ce->name,
 214                               line_terminator);
 215                }
 216        }
 217}
 218
 219static const char *ls_files_usage =
 220        "ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged])* "
 221        "[ --ignored [--exclude=<pattern>] [--exclude-from=<file>) ]";
 222
 223int main(int argc, char **argv)
 224{
 225        int i;
 226
 227        for (i = 1; i < argc; i++) {
 228                char *arg = argv[i];
 229
 230                if (!strcmp(arg, "-z")) {
 231                        line_terminator = 0;
 232                } else if (!strcmp(arg, "-t")) {
 233                        tag_cached = "H ";
 234                        tag_unmerged = "M ";
 235                        tag_removed = "R ";
 236                        tag_other = "? ";
 237                } else if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
 238                        show_cached = 1;
 239                } else if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
 240                        show_deleted = 1;
 241                } else if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
 242                        show_others = 1;
 243                } else if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
 244                        show_ignored = 1;
 245                } else if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
 246                        show_stage = 1;
 247                } else if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
 248                        /* There's no point in showing unmerged unless
 249                         * you also show the stage information.
 250                         */
 251                        show_stage = 1;
 252                        show_unmerged = 1;
 253                } else if (!strcmp(arg, "-x") && i+1 < argc) {
 254                        add_exclude(argv[++i]);
 255                } else if (!strncmp(arg, "--exclude=", 10)) {
 256                        add_exclude(arg+10);
 257                } else if (!strcmp(arg, "-X") && i+1 < argc) {
 258                        add_excludes_from_file(argv[++i]);
 259                } else if (!strncmp(arg, "--exclude-from=", 15)) {
 260                        add_excludes_from_file(arg+15);
 261                } else
 262                        usage(ls_files_usage);
 263        }
 264
 265        if (show_ignored && !nr_excludes) {
 266                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
 267                        argv[0]);
 268                exit(1);
 269        }
 270
 271        /* With no flags, we default to showing the cached files */
 272        if (!(show_stage | show_deleted | show_others | show_unmerged))
 273                show_cached = 1;
 274
 275        read_cache();
 276        show_files();
 277        return 0;
 278}