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