Merge branch 'jc/again'
[gitweb.git] / ls-files.c
index db2288aeee61bf64f30fdcc4b11e91c0cd3038a0..4a4af1ca3b3cc11e2996c2889cb8ea16bbb0dded 100644 (file)
@@ -11,6 +11,7 @@
 #include "cache.h"
 #include "quote.h"
 
+static int abbrev = 0;
 static int show_deleted = 0;
 static int show_cached = 0;
 static int show_others = 0;
@@ -19,11 +20,16 @@ static int show_stage = 0;
 static int show_unmerged = 0;
 static int show_modified = 0;
 static int show_killed = 0;
+static int show_other_directories = 0;
+static int hide_empty_directories = 0;
+static int show_valid_bit = 0;
 static int line_terminator = '\n';
 
 static int prefix_len = 0, prefix_offset = 0;
 static const char *prefix = NULL;
 static const char **pathspec = NULL;
+static int error_unmatch = 0;
+static char *ps_matched = NULL;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -88,11 +94,12 @@ static int add_excludes_from_file_1(const char *fname,
                close(fd);
                return 0;
        }
-       buf = xmalloc(size);
+       buf = xmalloc(size+1);
        if (read(fd, buf, size) != size)
                goto err;
        close(fd);
 
+       buf[size++] = '\n';
        entry = buf;
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
@@ -169,7 +176,7 @@ static int excluded_1(const char *pathname,
                        }
                        else {
                                /* match with FNM_PATHNAME:
-                                * exclude has base (baselen long) inplicitly
+                                * exclude has base (baselen long) implicitly
                                 * in front of it.
                                 */
                                int baselen = x->baselen;
@@ -208,7 +215,7 @@ static int excluded(const char *pathname)
 
 struct nond_on_fs {
        int len;
-       char name[0];
+       char name[FLEX_ARRAY]; /* more */
 };
 
 static struct nond_on_fs **dir;
@@ -233,6 +240,17 @@ static void add_name(const char *pathname, int len)
        dir[nr_dir++] = ent;
 }
 
+static int dir_exists(const char *dirname, int len)
+{
+       int pos = cache_name_pos(dirname, len);
+       if (pos >= 0)
+               return 1;
+       pos = -pos-1;
+       if (pos >= active_nr) /* can't */
+               return 0;
+       return !strncmp(active_cache[pos]->name, dirname, len);
+}
+
 /*
  * Read a directory tree. We currently ignore anything but
  * directories, regular files and symlinks. That's because git
@@ -242,11 +260,12 @@ static void add_name(const char *pathname, int len)
  * Also, we ignore the name ".git" (even if it is not a directory).
  * That likely will not change.
  */
-static void read_directory(const char *path, const char *base, int baselen)
+static int read_directory(const char *path, const char *base, int baselen)
 {
-       DIR *dir = opendir(path);
+       DIR *fdir = opendir(path);
+       int contents = 0;
 
-       if (dir) {
+       if (fdir) {
                int exclude_stk;
                struct dirent *de;
                char fullname[MAXPATHLEN + 1];
@@ -254,7 +273,7 @@ static void read_directory(const char *path, const char *base, int baselen)
 
                exclude_stk = push_exclude_per_directory(base, baselen);
 
-               while ((de = readdir(dir)) != NULL) {
+               while ((de = readdir(fdir)) != NULL) {
                        int len;
 
                        if ((de->d_name[0] == '.') &&
@@ -264,11 +283,15 @@ static void read_directory(const char *path, const char *base, int baselen)
                                continue;
                        len = strlen(de->d_name);
                        memcpy(fullname + baselen, de->d_name, len+1);
-                       if (excluded(fullname) != show_ignored)
-                               continue;
+                       if (excluded(fullname) != show_ignored) {
+                               if (!show_ignored || DTYPE(de) != DT_DIR) {
+                                       continue;
+                               }
+                       }
 
                        switch (DTYPE(de)) {
                        struct stat st;
+                       int subdir, rewind_base;
                        default:
                                continue;
                        case DT_UNKNOWN:
@@ -281,19 +304,33 @@ static void read_directory(const char *path, const char *base, int baselen)
                                /* fallthrough */
                        case DT_DIR:
                                memcpy(fullname + baselen + len, "/", 2);
-                               read_directory(fullname, fullname,
-                                              baselen + len + 1);
+                               len++;
+                               rewind_base = nr_dir;
+                               subdir = read_directory(fullname, fullname,
+                                                       baselen + len);
+                               if (show_other_directories &&
+                                   (subdir || !hide_empty_directories) &&
+                                   !dir_exists(fullname, baselen + len)) {
+                                       // Rewind the read subdirectory
+                                       while (nr_dir > rewind_base)
+                                               free(dir[--nr_dir]);
+                                       break;
+                               }
+                               contents += subdir;
                                continue;
                        case DT_REG:
                        case DT_LNK:
                                break;
                        }
                        add_name(fullname, baselen + len);
+                       contents++;
                }
-               closedir(dir);
+               closedir(fdir);
 
                pop_exclude_per_directory(exclude_stk);
        }
+
+       return contents;
 }
 
 static int cmp_name(const void *p1, const void *p2)
@@ -309,7 +346,8 @@ static int cmp_name(const void *p1, const void *p2)
  * Match a pathspec against a filename. The first "len" characters
  * are the common prefix
  */
-static int match(const char **spec, const char *filename, int len)
+static int match(const char **spec, char *ps_matched,
+                const char *filename, int len)
 {
        const char *m;
 
@@ -317,17 +355,24 @@ static int match(const char **spec, const char *filename, int len)
                int matchlen = strlen(m + len);
 
                if (!matchlen)
-                       return 1;
+                       goto matched;
                if (!strncmp(m + len, filename + len, matchlen)) {
                        if (m[len + matchlen - 1] == '/')
-                               return 1;
+                               goto matched;
                        switch (filename[len + matchlen]) {
                        case '/': case '\0':
-                               return 1;
+                               goto matched;
                        }
                }
                if (!fnmatch(m + len, filename + len, 0))
-                       return 1;
+                       goto matched;
+               if (ps_matched)
+                       ps_matched++;
+               continue;
+       matched:
+               if (ps_matched)
+                       *ps_matched = 1;
+               return 1;
        }
        return 0;
 }
@@ -340,11 +385,11 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
        if (len >= ent->len)
                die("git-ls-files: internal error - directory entry not superset of prefix");
 
-       if (pathspec && !match(pathspec, ent->name, len))
+       if (pathspec && !match(pathspec, ps_matched, ent->name, len))
                return;
 
        fputs(tag, stdout);
-       write_name_quoted("", ent->name + offset, line_terminator, stdout);
+       write_name_quoted("", 0, ent->name + offset, line_terminator, stdout);
        putchar(line_terminator);
 }
 
@@ -428,21 +473,41 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        if (len >= ce_namelen(ce))
                die("git-ls-files: internal error - cache entry not superset of prefix");
 
-       if (pathspec && !match(pathspec, ce->name, len))
+       if (pathspec && !match(pathspec, ps_matched, ce->name, len))
                return;
 
+       if (tag && *tag && show_valid_bit &&
+           (ce->ce_flags & htons(CE_VALID))) {
+               static char alttag[4];
+               memcpy(alttag, tag, 3);
+               if (isalpha(tag[0]))
+                       alttag[0] = tolower(tag[0]);
+               else if (tag[0] == '?')
+                       alttag[0] = '!';
+               else {
+                       alttag[0] = 'v';
+                       alttag[1] = tag[0];
+                       alttag[2] = ' ';
+                       alttag[3] = 0;
+               }
+               tag = alttag;
+       }
+
        if (!show_stage) {
                fputs(tag, stdout);
-               write_name_quoted("", ce->name + offset, line_terminator, stdout);
+               write_name_quoted("", 0, ce->name + offset,
+                                 line_terminator, stdout);
                putchar(line_terminator);
        }
        else {
                printf("%s%06o %s %d\t",
                       tag,
                       ntohl(ce->ce_mode),
-                      sha1_to_hex(ce->sha1),
+                      abbrev ? find_unique_abbrev(ce->sha1,abbrev)
+                               : sha1_to_hex(ce->sha1),
                       ce_stage(ce));
-               write_name_quoted("", ce->name + offset, line_terminator, stdout);
+               write_name_quoted("", 0, ce->name + offset,
+                                 line_terminator, stdout);
                putchar(line_terminator);
        }
 }
@@ -456,8 +521,28 @@ static void show_files(void)
                const char *path = ".", *base = "";
                int baselen = prefix_len;
 
-               if (baselen)
+               if (baselen) {
                        path = base = prefix;
+                       if (exclude_per_dir) {
+                               char *p, *pp = xmalloc(baselen+1);
+                               memcpy(pp, prefix, baselen+1);
+                               p = pp;
+                               while (1) {
+                                       char save = *p;
+                                       *p = 0;
+                                       push_exclude_per_directory(pp, p-pp);
+                                       *p++ = save;
+                                       if (!save)
+                                               break;
+                                       p = strchr(p, '/');
+                                       if (p)
+                                               p++;
+                                       else
+                                               p = pp + baselen;
+                               }
+                               free(pp);
+                       }
+               }
                read_directory(path, base, baselen);
                qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
                if (show_others)
@@ -485,7 +570,7 @@ static void show_files(void)
                        err = lstat(ce->name, &st);
                        if (show_deleted && err)
                                show_ce_entry(tag_removed, ce);
-                       if (show_modified && ce_modified(ce, &st))
+                       if (show_modified && ce_modified(ce, &st, 0))
                                show_ce_entry(tag_modified, ce);
                }
        }
@@ -558,9 +643,10 @@ static void verify_pathspec(void)
 }
 
 static const char ls_files_usage[] =
-       "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
+       "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
        "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
-       "[ --exclude-per-directory=<filename> ] [--] [<file>]*";
+       "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
+       "[--] [<file>]*";
 
 int main(int argc, const char **argv)
 {
@@ -583,13 +669,15 @@ int main(int argc, const char **argv)
                        line_terminator = 0;
                        continue;
                }
-               if (!strcmp(arg, "-t")) {
+               if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
                        tag_cached = "H ";
                        tag_unmerged = "M ";
                        tag_removed = "R ";
                        tag_modified = "C ";
                        tag_other = "? ";
                        tag_killed = "K ";
+                       if (arg[1] == 'v')
+                               show_valid_bit = 1;
                        continue;
                }
                if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
@@ -620,6 +708,14 @@ int main(int argc, const char **argv)
                        show_killed = 1;
                        continue;
                }
+               if (!strcmp(arg, "--directory")) {
+                       show_other_directories = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--no-empty-directory")) {
+                       hide_empty_directories = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
                        /* There's no point in showing unmerged unless
                         * you also show the stage information.
@@ -657,6 +753,22 @@ int main(int argc, const char **argv)
                        prefix_offset = 0;
                        continue;
                }
+               if (!strcmp(arg, "--error-unmatch")) {
+                       error_unmatch = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--abbrev=", 9)) {
+                       abbrev = strtoul(arg+9, NULL, 10);
+                       if (abbrev && abbrev < MINIMUM_ABBREV)
+                               abbrev = MINIMUM_ABBREV;
+                       else if (abbrev > 40)
+                               abbrev = 40;
+                       continue;
+               }
+               if (!strcmp(arg, "--abbrev")) {
+                       abbrev = DEFAULT_ABBREV;
+                       continue;
+               }
                if (*arg == '-')
                        usage(ls_files_usage);
                break;
@@ -668,6 +780,14 @@ int main(int argc, const char **argv)
        if (pathspec)
                verify_pathspec();
 
+       /* Treat unmatching pathspec elements as errors */
+       if (pathspec && error_unmatch) {
+               int num;
+               for (num = 0; pathspec[num]; num++)
+                       ;
+               ps_matched = xcalloc(1, num);
+       }
+
        if (show_ignored && !exc_given) {
                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
                        argv[0]);
@@ -683,5 +803,21 @@ int main(int argc, const char **argv)
        if (prefix)
                prune_cache();
        show_files();
+
+       if (ps_matched) {
+               /* We need to make sure all pathspec matched otherwise
+                * it is an error.
+                */
+               int num, errors = 0;
+               for (num = 0; pathspec[num]; num++) {
+                       if (ps_matched[num])
+                               continue;
+                       error("pathspec '%s' did not match any.",
+                             pathspec[num] + prefix_offset);
+                       errors++;
+               }
+               return errors ? 1 : 0;
+       }
+
        return 0;
 }