builtin-ls-files.con commit t9400: Work around CVS' deficiencies (b3c81cf)
   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 "cache.h"
   9#include "quote.h"
  10#include "dir.h"
  11#include "builtin.h"
  12
  13static int abbrev;
  14static int show_deleted;
  15static int show_cached;
  16static int show_others;
  17static int show_stage;
  18static int show_unmerged;
  19static int show_modified;
  20static int show_killed;
  21static int show_valid_bit;
  22static int line_terminator = '\n';
  23
  24static int prefix_len;
  25static int prefix_offset;
  26static const char **pathspec;
  27static int error_unmatch;
  28static char *ps_matched;
  29
  30static const char *tag_cached = "";
  31static const char *tag_unmerged = "";
  32static const char *tag_removed = "";
  33static const char *tag_other = "";
  34static const char *tag_killed = "";
  35static const char *tag_modified = "";
  36
  37
  38/*
  39 * Match a pathspec against a filename. The first "len" characters
  40 * are the common prefix
  41 */
  42static int match(const char **spec, char *ps_matched,
  43                 const char *filename, int len)
  44{
  45        const char *m;
  46
  47        while ((m = *spec++) != NULL) {
  48                int matchlen = strlen(m + len);
  49
  50                if (!matchlen)
  51                        goto matched;
  52                if (!strncmp(m + len, filename + len, matchlen)) {
  53                        if (m[len + matchlen - 1] == '/')
  54                                goto matched;
  55                        switch (filename[len + matchlen]) {
  56                        case '/': case '\0':
  57                                goto matched;
  58                        }
  59                }
  60                if (!fnmatch(m + len, filename + len, 0))
  61                        goto matched;
  62                if (ps_matched)
  63                        ps_matched++;
  64                continue;
  65        matched:
  66                if (ps_matched)
  67                        *ps_matched = 1;
  68                return 1;
  69        }
  70        return 0;
  71}
  72
  73static void show_dir_entry(const char *tag, struct dir_entry *ent)
  74{
  75        int len = prefix_len;
  76        int offset = prefix_offset;
  77
  78        if (len >= ent->len)
  79                die("git-ls-files: internal error - directory entry not superset of prefix");
  80
  81        if (pathspec && !match(pathspec, ps_matched, ent->name, len))
  82                return;
  83
  84        fputs(tag, stdout);
  85        write_name_quoted("", 0, ent->name + offset, line_terminator, stdout);
  86        putchar(line_terminator);
  87}
  88
  89static void show_other_files(struct dir_struct *dir)
  90{
  91        int i;
  92
  93
  94        /*
  95         * Skip matching and unmerged entries for the paths,
  96         * since we want just "others".
  97         *
  98         * (Matching entries are normally pruned during
  99         * the directory tree walk, but will show up for
 100         * gitlinks because we don't necessarily have
 101         * dir->show_other_directories set to suppress
 102         * them).
 103         */
 104        for (i = 0; i < dir->nr; i++) {
 105                struct dir_entry *ent = dir->entries[i];
 106                int len, pos;
 107                struct cache_entry *ce;
 108
 109                /*
 110                 * Remove the '/' at the end that directory
 111                 * walking adds for directory entries.
 112                 */
 113                len = ent->len;
 114                if (len && ent->name[len-1] == '/')
 115                        len--;
 116                pos = cache_name_pos(ent->name, len);
 117                if (0 <= pos)
 118                        continue;       /* exact match */
 119                pos = -pos - 1;
 120                if (pos < active_nr) { 
 121                        ce = active_cache[pos];
 122                        if (ce_namelen(ce) == len &&
 123                            !memcmp(ce->name, ent->name, len))
 124                                continue; /* Yup, this one exists unmerged */
 125                }
 126                show_dir_entry(tag_other, ent);
 127        }
 128}
 129
 130static void show_killed_files(struct dir_struct *dir)
 131{
 132        int i;
 133        for (i = 0; i < dir->nr; i++) {
 134                struct dir_entry *ent = dir->entries[i];
 135                char *cp, *sp;
 136                int pos, len, killed = 0;
 137
 138                for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
 139                        sp = strchr(cp, '/');
 140                        if (!sp) {
 141                                /* If ent->name is prefix of an entry in the
 142                                 * cache, it will be killed.
 143                                 */
 144                                pos = cache_name_pos(ent->name, ent->len);
 145                                if (0 <= pos)
 146                                        die("bug in show-killed-files");
 147                                pos = -pos - 1;
 148                                while (pos < active_nr &&
 149                                       ce_stage(active_cache[pos]))
 150                                        pos++; /* skip unmerged */
 151                                if (active_nr <= pos)
 152                                        break;
 153                                /* pos points at a name immediately after
 154                                 * ent->name in the cache.  Does it expect
 155                                 * ent->name to be a directory?
 156                                 */
 157                                len = ce_namelen(active_cache[pos]);
 158                                if ((ent->len < len) &&
 159                                    !strncmp(active_cache[pos]->name,
 160                                             ent->name, ent->len) &&
 161                                    active_cache[pos]->name[ent->len] == '/')
 162                                        killed = 1;
 163                                break;
 164                        }
 165                        if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
 166                                /* If any of the leading directories in
 167                                 * ent->name is registered in the cache,
 168                                 * ent->name will be killed.
 169                                 */
 170                                killed = 1;
 171                                break;
 172                        }
 173                }
 174                if (killed)
 175                        show_dir_entry(tag_killed, dir->entries[i]);
 176        }
 177}
 178
 179static void show_ce_entry(const char *tag, struct cache_entry *ce)
 180{
 181        int len = prefix_len;
 182        int offset = prefix_offset;
 183
 184        if (len >= ce_namelen(ce))
 185                die("git-ls-files: internal error - cache entry not superset of prefix");
 186
 187        if (pathspec && !match(pathspec, ps_matched, ce->name, len))
 188                return;
 189
 190        if (tag && *tag && show_valid_bit &&
 191            (ce->ce_flags & htons(CE_VALID))) {
 192                static char alttag[4];
 193                memcpy(alttag, tag, 3);
 194                if (isalpha(tag[0]))
 195                        alttag[0] = tolower(tag[0]);
 196                else if (tag[0] == '?')
 197                        alttag[0] = '!';
 198                else {
 199                        alttag[0] = 'v';
 200                        alttag[1] = tag[0];
 201                        alttag[2] = ' ';
 202                        alttag[3] = 0;
 203                }
 204                tag = alttag;
 205        }
 206
 207        if (!show_stage) {
 208                fputs(tag, stdout);
 209                write_name_quoted("", 0, ce->name + offset,
 210                                  line_terminator, stdout);
 211                putchar(line_terminator);
 212        }
 213        else {
 214                printf("%s%06o %s %d\t",
 215                       tag,
 216                       ntohl(ce->ce_mode),
 217                       abbrev ? find_unique_abbrev(ce->sha1,abbrev)
 218                                : sha1_to_hex(ce->sha1),
 219                       ce_stage(ce));
 220                write_name_quoted("", 0, ce->name + offset,
 221                                  line_terminator, stdout);
 222                putchar(line_terminator);
 223        }
 224}
 225
 226static void show_files(struct dir_struct *dir, const char *prefix)
 227{
 228        int i;
 229
 230        /* For cached/deleted files we don't need to even do the readdir */
 231        if (show_others || show_killed) {
 232                const char *path = ".", *base = "";
 233                int baselen = prefix_len;
 234
 235                if (baselen)
 236                        path = base = prefix;
 237                read_directory(dir, path, base, baselen, pathspec);
 238                if (show_others)
 239                        show_other_files(dir);
 240                if (show_killed)
 241                        show_killed_files(dir);
 242        }
 243        if (show_cached | show_stage) {
 244                for (i = 0; i < active_nr; i++) {
 245                        struct cache_entry *ce = active_cache[i];
 246                        if (excluded(dir, ce->name) != dir->show_ignored)
 247                                continue;
 248                        if (show_unmerged && !ce_stage(ce))
 249                                continue;
 250                        show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
 251                }
 252        }
 253        if (show_deleted | show_modified) {
 254                for (i = 0; i < active_nr; i++) {
 255                        struct cache_entry *ce = active_cache[i];
 256                        struct stat st;
 257                        int err;
 258                        if (excluded(dir, ce->name) != dir->show_ignored)
 259                                continue;
 260                        err = lstat(ce->name, &st);
 261                        if (show_deleted && err)
 262                                show_ce_entry(tag_removed, ce);
 263                        if (show_modified && ce_modified(ce, &st, 0))
 264                                show_ce_entry(tag_modified, ce);
 265                }
 266        }
 267}
 268
 269/*
 270 * Prune the index to only contain stuff starting with "prefix"
 271 */
 272static void prune_cache(const char *prefix)
 273{
 274        int pos = cache_name_pos(prefix, prefix_len);
 275        unsigned int first, last;
 276
 277        if (pos < 0)
 278                pos = -pos-1;
 279        active_cache += pos;
 280        active_nr -= pos;
 281        first = 0;
 282        last = active_nr;
 283        while (last > first) {
 284                int next = (last + first) >> 1;
 285                struct cache_entry *ce = active_cache[next];
 286                if (!strncmp(ce->name, prefix, prefix_len)) {
 287                        first = next+1;
 288                        continue;
 289                }
 290                last = next;
 291        }
 292        active_nr = last;
 293}
 294
 295static const char *verify_pathspec(const char *prefix)
 296{
 297        const char **p, *n, *prev;
 298        char *real_prefix;
 299        unsigned long max;
 300
 301        prev = NULL;
 302        max = PATH_MAX;
 303        for (p = pathspec; (n = *p) != NULL; p++) {
 304                int i, len = 0;
 305                for (i = 0; i < max; i++) {
 306                        char c = n[i];
 307                        if (prev && prev[i] != c)
 308                                break;
 309                        if (!c || c == '*' || c == '?')
 310                                break;
 311                        if (c == '/')
 312                                len = i+1;
 313                }
 314                prev = n;
 315                if (len < max) {
 316                        max = len;
 317                        if (!max)
 318                                break;
 319                }
 320        }
 321
 322        if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
 323                die("git-ls-files: cannot generate relative filenames containing '..'");
 324
 325        real_prefix = NULL;
 326        prefix_len = max;
 327        if (max) {
 328                real_prefix = xmalloc(max + 1);
 329                memcpy(real_prefix, prev, max);
 330                real_prefix[max] = 0;
 331        }
 332        return real_prefix;
 333}
 334
 335static const char ls_files_usage[] =
 336        "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
 337        "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
 338        "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
 339        "[--] [<file>]*";
 340
 341int cmd_ls_files(int argc, const char **argv, const char *prefix)
 342{
 343        int i;
 344        int exc_given = 0, require_work_tree = 0;
 345        struct dir_struct dir;
 346
 347        memset(&dir, 0, sizeof(dir));
 348        if (prefix)
 349                prefix_offset = strlen(prefix);
 350        git_config(git_default_config);
 351
 352        for (i = 1; i < argc; i++) {
 353                const char *arg = argv[i];
 354
 355                if (!strcmp(arg, "--")) {
 356                        i++;
 357                        break;
 358                }
 359                if (!strcmp(arg, "-z")) {
 360                        line_terminator = 0;
 361                        continue;
 362                }
 363                if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
 364                        tag_cached = "H ";
 365                        tag_unmerged = "M ";
 366                        tag_removed = "R ";
 367                        tag_modified = "C ";
 368                        tag_other = "? ";
 369                        tag_killed = "K ";
 370                        if (arg[1] == 'v')
 371                                show_valid_bit = 1;
 372                        continue;
 373                }
 374                if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
 375                        show_cached = 1;
 376                        continue;
 377                }
 378                if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
 379                        show_deleted = 1;
 380                        continue;
 381                }
 382                if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
 383                        show_modified = 1;
 384                        require_work_tree = 1;
 385                        continue;
 386                }
 387                if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
 388                        show_others = 1;
 389                        require_work_tree = 1;
 390                        continue;
 391                }
 392                if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
 393                        dir.show_ignored = 1;
 394                        require_work_tree = 1;
 395                        continue;
 396                }
 397                if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
 398                        show_stage = 1;
 399                        continue;
 400                }
 401                if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
 402                        show_killed = 1;
 403                        require_work_tree = 1;
 404                        continue;
 405                }
 406                if (!strcmp(arg, "--directory")) {
 407                        dir.show_other_directories = 1;
 408                        continue;
 409                }
 410                if (!strcmp(arg, "--no-empty-directory")) {
 411                        dir.hide_empty_directories = 1;
 412                        continue;
 413                }
 414                if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
 415                        /* There's no point in showing unmerged unless
 416                         * you also show the stage information.
 417                         */
 418                        show_stage = 1;
 419                        show_unmerged = 1;
 420                        continue;
 421                }
 422                if (!strcmp(arg, "-x") && i+1 < argc) {
 423                        exc_given = 1;
 424                        add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
 425                        continue;
 426                }
 427                if (!prefixcmp(arg, "--exclude=")) {
 428                        exc_given = 1;
 429                        add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
 430                        continue;
 431                }
 432                if (!strcmp(arg, "-X") && i+1 < argc) {
 433                        exc_given = 1;
 434                        add_excludes_from_file(&dir, argv[++i]);
 435                        continue;
 436                }
 437                if (!prefixcmp(arg, "--exclude-from=")) {
 438                        exc_given = 1;
 439                        add_excludes_from_file(&dir, arg+15);
 440                        continue;
 441                }
 442                if (!prefixcmp(arg, "--exclude-per-directory=")) {
 443                        exc_given = 1;
 444                        dir.exclude_per_dir = arg + 24;
 445                        continue;
 446                }
 447                if (!strcmp(arg, "--full-name")) {
 448                        prefix_offset = 0;
 449                        continue;
 450                }
 451                if (!strcmp(arg, "--error-unmatch")) {
 452                        error_unmatch = 1;
 453                        continue;
 454                }
 455                if (!prefixcmp(arg, "--abbrev=")) {
 456                        abbrev = strtoul(arg+9, NULL, 10);
 457                        if (abbrev && abbrev < MINIMUM_ABBREV)
 458                                abbrev = MINIMUM_ABBREV;
 459                        else if (abbrev > 40)
 460                                abbrev = 40;
 461                        continue;
 462                }
 463                if (!strcmp(arg, "--abbrev")) {
 464                        abbrev = DEFAULT_ABBREV;
 465                        continue;
 466                }
 467                if (*arg == '-')
 468                        usage(ls_files_usage);
 469                break;
 470        }
 471
 472        if (require_work_tree &&
 473                        (is_bare_repository() || is_inside_git_dir()))
 474                die("This operation must be run in a work tree");
 475
 476        pathspec = get_pathspec(prefix, argv + i);
 477
 478        /* Verify that the pathspec matches the prefix */
 479        if (pathspec)
 480                prefix = verify_pathspec(prefix);
 481
 482        /* Treat unmatching pathspec elements as errors */
 483        if (pathspec && error_unmatch) {
 484                int num;
 485                for (num = 0; pathspec[num]; num++)
 486                        ;
 487                ps_matched = xcalloc(1, num);
 488        }
 489
 490        if (dir.show_ignored && !exc_given) {
 491                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
 492                        argv[0]);
 493                exit(1);
 494        }
 495
 496        /* With no flags, we default to showing the cached files */
 497        if (!(show_stage | show_deleted | show_others | show_unmerged |
 498              show_killed | show_modified))
 499                show_cached = 1;
 500
 501        read_cache();
 502        if (prefix)
 503                prune_cache(prefix);
 504        show_files(&dir, prefix);
 505
 506        if (ps_matched) {
 507                /* We need to make sure all pathspec matched otherwise
 508                 * it is an error.
 509                 */
 510                int num, errors = 0;
 511                for (num = 0; pathspec[num]; num++) {
 512                        if (ps_matched[num])
 513                                continue;
 514                        error("pathspec '%s' did not match any file(s) known to git.",
 515                              pathspec[num] + prefix_offset);
 516                        errors++;
 517                }
 518
 519                if (errors)
 520                        fprintf(stderr, "Did you forget to 'git add'?\n");
 521
 522                return errors ? 1 : 0;
 523        }
 524
 525        return 0;
 526}