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