Merge branch 'jk/xdiff-drop-xdl-fast-hash'
[gitweb.git] / builtin / ls-files.c
index b6a7cb0c7c48293c6348806cb342a9289f146740..1592290815c8b93701fa9d175d76f7a7ed85f7d2 100644 (file)
@@ -14,6 +14,7 @@
 #include "resolve-undo.h"
 #include "string-list.h"
 #include "pathspec.h"
+#include "run-command.h"
 
 static int abbrev;
 static int show_deleted;
@@ -27,8 +28,12 @@ static int show_killed;
 static int show_valid_bit;
 static int line_terminator = '\n';
 static int debug_mode;
+static int show_eol;
+static int recurse_submodules;
+static struct argv_array submodules_options = ARGV_ARRAY_INIT;
 
 static const char *prefix;
+static const char *super_prefix;
 static int max_prefix_len;
 static int prefix_len;
 static struct pathspec pathspec;
@@ -47,14 +52,44 @@ static const char *tag_modified = "";
 static const char *tag_skip_worktree = "";
 static const char *tag_resolve_undo = "";
 
+static void write_eolinfo(const struct cache_entry *ce, const char *path)
+{
+       if (!show_eol)
+               return;
+       else {
+               struct stat st;
+               const char *i_txt = "";
+               const char *w_txt = "";
+               const char *a_txt = get_convert_attr_ascii(path);
+               if (ce && S_ISREG(ce->ce_mode))
+                       i_txt = get_cached_convert_stats_ascii(ce->name);
+               if (!lstat(path, &st) && S_ISREG(st.st_mode))
+                       w_txt = get_wt_convert_stats_ascii(path);
+               printf("i/%-5s w/%-5s attr/%-17s\t", i_txt, w_txt, a_txt);
+       }
+}
+
 static void write_name(const char *name)
 {
+       /*
+        * Prepend the super_prefix to name to construct the full_name to be
+        * written.
+        */
+       struct strbuf full_name = STRBUF_INIT;
+       if (super_prefix) {
+               strbuf_addstr(&full_name, super_prefix);
+               strbuf_addstr(&full_name, name);
+               name = full_name.buf;
+       }
+
        /*
         * With "--full-name", prefix_len=0; this caller needs to pass
         * an empty string in that case (a NULL is good for "").
         */
        write_name_quoted_relative(name, prefix_len ? prefix : NULL,
                                   stdout, line_terminator);
+
+       strbuf_release(&full_name);
 }
 
 static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@ -68,6 +103,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
                return;
 
        fputs(tag, stdout);
+       write_eolinfo(NULL, ent->name);
        write_name(ent->name);
 }
 
@@ -99,7 +135,8 @@ static void show_killed_files(struct dir_struct *dir)
                                 */
                                pos = cache_name_pos(ent->name, ent->len);
                                if (0 <= pos)
-                                       die("bug in show-killed-files");
+                                       die("BUG: killed-file %.*s not found",
+                                               ent->len, ent->name);
                                pos = -pos - 1;
                                while (pos < active_nr &&
                                       ce_stage(active_cache[pos]))
@@ -132,54 +169,117 @@ static void show_killed_files(struct dir_struct *dir)
        }
 }
 
+/*
+ * Compile an argv_array with all of the options supported by --recurse_submodules
+ */
+static void compile_submodule_options(const struct dir_struct *dir, int show_tag)
+{
+       if (line_terminator == '\0')
+               argv_array_push(&submodules_options, "-z");
+       if (show_tag)
+               argv_array_push(&submodules_options, "-t");
+       if (show_valid_bit)
+               argv_array_push(&submodules_options, "-v");
+       if (show_cached)
+               argv_array_push(&submodules_options, "--cached");
+       if (show_eol)
+               argv_array_push(&submodules_options, "--eol");
+       if (debug_mode)
+               argv_array_push(&submodules_options, "--debug");
+}
+
+/**
+ * Recursively call ls-files on a submodule
+ */
+static void show_gitlink(const struct cache_entry *ce)
+{
+       struct child_process cp = CHILD_PROCESS_INIT;
+       int status;
+       int i;
+
+       argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
+                        super_prefix ? super_prefix : "",
+                        ce->name);
+       argv_array_push(&cp.args, "ls-files");
+       argv_array_push(&cp.args, "--recurse-submodules");
+
+       /* add supported options */
+       argv_array_pushv(&cp.args, submodules_options.argv);
+
+       /*
+        * Pass in the original pathspec args.  The submodule will be
+        * responsible for prepending the 'submodule_prefix' prior to comparing
+        * against the pathspec for matches.
+        */
+       argv_array_push(&cp.args, "--");
+       for (i = 0; i < pathspec.nr; i++)
+               argv_array_push(&cp.args, pathspec.items[i].original);
+
+       cp.git_cmd = 1;
+       cp.dir = ce->name;
+       status = run_command(&cp);
+       if (status)
+               exit(status);
+}
+
 static void show_ce_entry(const char *tag, const struct cache_entry *ce)
 {
+       struct strbuf name = STRBUF_INIT;
        int len = max_prefix_len;
+       if (super_prefix)
+               strbuf_addstr(&name, super_prefix);
+       strbuf_addstr(&name, ce->name);
 
        if (len >= ce_namelen(ce))
                die("git ls-files: internal error - cache entry not superset of prefix");
 
-       if (!match_pathspec(&pathspec, ce->name, ce_namelen(ce),
-                           len, ps_matched,
-                           S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
-               return;
+       if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
+           submodule_path_match(&pathspec, name.buf, ps_matched)) {
+               show_gitlink(ce);
+       } else if (match_pathspec(&pathspec, name.buf, name.len,
+                                 len, ps_matched,
+                                 S_ISDIR(ce->ce_mode) ||
+                                 S_ISGITLINK(ce->ce_mode))) {
+               if (tag && *tag && show_valid_bit &&
+                   (ce->ce_flags & 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 (tag && *tag && show_valid_bit &&
-           (ce->ce_flags & 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;
+               if (!show_stage) {
+                       fputs(tag, stdout);
+               } else {
+                       printf("%s%06o %s %d\t",
+                              tag,
+                              ce->ce_mode,
+                              find_unique_abbrev(ce->oid.hash, abbrev),
+                              ce_stage(ce));
+               }
+               write_eolinfo(ce, ce->name);
+               write_name(ce->name);
+               if (debug_mode) {
+                       const struct stat_data *sd = &ce->ce_stat_data;
+
+                       printf("  ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
+                       printf("  mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
+                       printf("  dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
+                       printf("  uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
+                       printf("  size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
                }
-               tag = alttag;
        }
 
-       if (!show_stage) {
-               fputs(tag, stdout);
-       } else {
-               printf("%s%06o %s %d\t",
-                      tag,
-                      ce->ce_mode,
-                      find_unique_abbrev(ce->sha1,abbrev),
-                      ce_stage(ce));
-       }
-       write_name(ce->name);
-       if (debug_mode) {
-               const struct stat_data *sd = &ce->ce_stat_data;
-
-               printf("  ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
-               printf("  mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
-               printf("  dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
-               printf("  uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
-               printf("  size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
-       }
+       strbuf_release(&name);
 }
 
 static void show_ru_info(void)
@@ -359,14 +459,6 @@ static const char * const ls_files_usage[] = {
        NULL
 };
 
-static int option_parse_z(const struct option *opt,
-                         const char *arg, int unset)
-{
-       line_terminator = unset ? '\n' : '\0';
-
-       return 0;
-}
-
 static int option_parse_exclude(const struct option *opt,
                                const char *arg, int unset)
 {
@@ -408,9 +500,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
        struct exclude_list *el;
        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
        struct option builtin_ls_files_options[] = {
-               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
-                       N_("paths are separated with NUL character"),
-                       PARSE_OPT_NOARG, option_parse_z },
+               /* Think twice before adding "--nul" synonym to this */
+               OPT_SET_INT('z', NULL, &line_terminator,
+                       N_("paths are separated with NUL character"), '\0'),
                OPT_BOOL('t', NULL, &show_tag,
                        N_("identify the file status with tags")),
                OPT_BOOL('v', NULL, &show_valid_bit,
@@ -433,6 +525,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                OPT_BIT(0, "directory", &dir.flags,
                        N_("show 'other' directories' names only"),
                        DIR_SHOW_OTHER_DIRECTORIES),
+               OPT_BOOL(0, "eol", &show_eol, N_("show line endings of files")),
                OPT_NEGBIT(0, "empty-directory", &dir.flags,
                        N_("don't show empty directories"),
                        DIR_HIDE_EMPTY_DIRECTORIES),
@@ -454,6 +547,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
                        N_("make the output relative to the project top directory"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
+               OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
+                       N_("recurse through submodules")),
                OPT_BOOL(0, "error-unmatch", &error_unmatch,
                        N_("if any <file> is not in the index, treat this as an error")),
                OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
@@ -470,6 +565,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
        prefix = cmd_prefix;
        if (prefix)
                prefix_len = strlen(prefix);
+       super_prefix = get_super_prefix();
        git_config(git_default_config, NULL);
 
        if (read_cache() < 0)
@@ -505,13 +601,32 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
        if (require_work_tree && !is_inside_work_tree())
                setup_work_tree();
 
+       if (recurse_submodules)
+               compile_submodule_options(&dir, show_tag);
+
+       if (recurse_submodules &&
+           (show_stage || show_deleted || show_others || show_unmerged ||
+            show_killed || show_modified || show_resolve_undo || with_tree))
+               die("ls-files --recurse-submodules unsupported mode");
+
+       if (recurse_submodules && error_unmatch)
+               die("ls-files --recurse-submodules does not support "
+                   "--error-unmatch");
+
        parse_pathspec(&pathspec, 0,
                       PATHSPEC_PREFER_CWD |
                       PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
                       prefix, argv);
 
-       /* Find common prefix for all pathspec's */
-       max_prefix = common_prefix(&pathspec);
+       /*
+        * Find common prefix for all pathspec's
+        * This is used as a performance optimization which unfortunately cannot
+        * be done when recursing into submodules
+        */
+       if (recurse_submodules)
+               max_prefix = NULL;
+       else
+               max_prefix = common_prefix(&pathspec);
        max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
 
        /* Treat unmatching pathspec elements as errors */