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