Code

remote-curl: ensure that URLs have a trailing slash
[git.git] / builtin-ls-files.c
index ca6f33d0466572863db7dbd4c3079dcb06caa4e0..b065061392718e4250d2c52dc3d53a0c1c7938e9 100644 (file)
@@ -10,6 +10,9 @@
 #include "dir.h"
 #include "builtin.h"
 #include "tree.h"
+#include "parse-options.h"
+#include "resolve-undo.h"
+#include "string-list.h"
 
 static int abbrev;
 static int show_deleted;
@@ -17,6 +20,7 @@ static int show_cached;
 static int show_others;
 static int show_stage;
 static int show_unmerged;
+static int show_resolve_undo;
 static int show_modified;
 static int show_killed;
 static int show_valid_bit;
@@ -28,6 +32,7 @@ static const char **pathspec;
 static int error_unmatch;
 static char *ps_matched;
 static const char *with_tree;
+static int exc_given;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -35,6 +40,8 @@ static const char *tag_removed = "";
 static const char *tag_other = "";
 static const char *tag_killed = "";
 static const char *tag_modified = "";
+static const char *tag_skip_worktree = "";
+static const char *tag_resolve_undo = "";
 
 static void show_dir_entry(const char *tag, struct dir_entry *ent)
 {
@@ -153,18 +160,45 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        write_name_quoted(ce->name + offset, stdout, line_terminator);
 }
 
+static int show_one_ru(struct string_list_item *item, void *cbdata)
+{
+       int offset = prefix_offset;
+       const char *path = item->string;
+       struct resolve_undo_info *ui = item->util;
+       int i, len;
+
+       len = strlen(path);
+       if (len < prefix_len)
+               return 0; /* outside of the prefix */
+       if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched))
+               return 0; /* uninterested */
+       for (i = 0; i < 3; i++) {
+               if (!ui->mode[i])
+                       continue;
+               printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
+                      abbrev
+                      ? find_unique_abbrev(ui->sha1[i], abbrev)
+                      : sha1_to_hex(ui->sha1[i]),
+                      i + 1);
+               write_name_quoted(path + offset, stdout, line_terminator);
+       }
+       return 0;
+}
+
+static void show_ru_info(const char *prefix)
+{
+       if (!the_index.resolve_undo)
+               return;
+       for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
+}
+
 static void show_files(struct dir_struct *dir, const char *prefix)
 {
        int i;
 
        /* For cached/deleted files we don't need to even do the readdir */
        if (show_others || show_killed) {
-               const char *path = ".", *base = "";
-               int baselen = prefix_len;
-
-               if (baselen)
-                       path = base = prefix;
-               read_directory(dir, path, base, baselen, pathspec);
+               fill_directory(dir, pathspec);
                if (show_others)
                        show_other_files(dir);
                if (show_killed)
@@ -174,13 +208,15 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        int dtype = ce_to_dtype(ce);
-                       if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
+                       if (dir->flags & DIR_SHOW_IGNORED &&
+                           !excluded(dir, ce->name, &dtype))
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
                        if (ce->ce_flags & CE_UPDATE)
                                continue;
-                       show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
+                       show_ce_entry(ce_stage(ce) ? tag_unmerged :
+                               (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
                }
        }
        if (show_deleted | show_modified) {
@@ -189,10 +225,13 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                        struct stat st;
                        int err;
                        int dtype = ce_to_dtype(ce);
-                       if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
+                       if (dir->flags & DIR_SHOW_IGNORED &&
+                           !excluded(dir, ce->name, &dtype))
                                continue;
                        if (ce->ce_flags & CE_UPDATE)
                                continue;
+                       if (ce_skip_worktree(ce))
+                               continue;
                        err = lstat(ce->name, &st);
                        if (show_deleted && err)
                                show_ce_entry(tag_removed, ce);
@@ -374,160 +413,148 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
        return errors;
 }
 
-static const char ls_files_usage[] =
-       "git ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
-       "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
-       "[ --exclude-per-directory=<filename> ] [--exclude-standard] "
-       "[--full-name] [--abbrev] [--] [<file>]*";
+static const char * const ls_files_usage[] = {
+       "git ls-files [options] [<file>]*",
+       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)
+{
+       struct exclude_list *list = opt->value;
+
+       exc_given = 1;
+       add_exclude(arg, "", 0, list);
+
+       return 0;
+}
+
+static int option_parse_exclude_from(const struct option *opt,
+                                    const char *arg, int unset)
+{
+       struct dir_struct *dir = opt->value;
+
+       exc_given = 1;
+       add_excludes_from_file(dir, arg);
+
+       return 0;
+}
+
+static int option_parse_exclude_standard(const struct option *opt,
+                                        const char *arg, int unset)
+{
+       struct dir_struct *dir = opt->value;
+
+       exc_given = 1;
+       setup_standard_excludes(dir);
+
+       return 0;
+}
 
 int cmd_ls_files(int argc, const char **argv, const char *prefix)
 {
-       int i;
-       int exc_given = 0, require_work_tree = 0;
+       int require_work_tree = 0, show_tag = 0;
        struct dir_struct dir;
+       struct option builtin_ls_files_options[] = {
+               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+                       "paths are separated with NUL character",
+                       PARSE_OPT_NOARG, option_parse_z },
+               OPT_BOOLEAN('t', NULL, &show_tag,
+                       "identify the file status with tags"),
+               OPT_BOOLEAN('v', NULL, &show_valid_bit,
+                       "use lowercase letters for 'assume unchanged' files"),
+               OPT_BOOLEAN('c', "cached", &show_cached,
+                       "show cached files in the output (default)"),
+               OPT_BOOLEAN('d', "deleted", &show_deleted,
+                       "show deleted files in the output"),
+               OPT_BOOLEAN('m', "modified", &show_modified,
+                       "show modified files in the output"),
+               OPT_BOOLEAN('o', "others", &show_others,
+                       "show other files in the output"),
+               OPT_BIT('i', "ignored", &dir.flags,
+                       "show ignored files in the output",
+                       DIR_SHOW_IGNORED),
+               OPT_BOOLEAN('s', "stage", &show_stage,
+                       "show staged contents' object name in the output"),
+               OPT_BOOLEAN('k', "killed", &show_killed,
+                       "show files on the filesystem that need to be removed"),
+               OPT_BIT(0, "directory", &dir.flags,
+                       "show 'other' directories' name only",
+                       DIR_SHOW_OTHER_DIRECTORIES),
+               OPT_NEGBIT(0, "empty-directory", &dir.flags,
+                       "don't show empty directories",
+                       DIR_HIDE_EMPTY_DIRECTORIES),
+               OPT_BOOLEAN('u', "unmerged", &show_unmerged,
+                       "show unmerged files in the output"),
+               OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
+                           "show resolve-undo information"),
+               { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
+                       "skip files matching pattern",
+                       0, option_parse_exclude },
+               { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
+                       "exclude patterns are read from <file>",
+                       0, option_parse_exclude_from },
+               OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
+                       "read additional per-directory exclude patterns in <file>"),
+               { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
+                       "add the standard git exclusions",
+                       PARSE_OPT_NOARG, option_parse_exclude_standard },
+               { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL,
+                       "make the output relative to the project top directory",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
+               OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
+                       "if any <file> is not in the index, treat this as an error"),
+               OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
+                       "pretend that paths removed since <tree-ish> are still present"),
+               OPT__ABBREV(&abbrev),
+               OPT_END()
+       };
 
        memset(&dir, 0, sizeof(dir));
        if (prefix)
                prefix_offset = strlen(prefix);
        git_config(git_default_config, NULL);
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-z")) {
-                       line_terminator = 0;
-                       continue;
-               }
-               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")) {
-                       show_cached = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
-                       show_deleted = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
-                       show_modified = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
-                       show_others = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
-                       dir.show_ignored = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
-                       show_stage = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
-                       show_killed = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--directory")) {
-                       dir.show_other_directories = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--no-empty-directory")) {
-                       dir.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.
-                        */
-                       show_stage = 1;
-                       show_unmerged = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-x") && i+1 < argc) {
-                       exc_given = 1;
-                       add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--exclude=")) {
-                       exc_given = 1;
-                       add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
-                       continue;
-               }
-               if (!strcmp(arg, "-X") && i+1 < argc) {
-                       exc_given = 1;
-                       add_excludes_from_file(&dir, argv[++i]);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--exclude-from=")) {
-                       exc_given = 1;
-                       add_excludes_from_file(&dir, arg+15);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--exclude-per-directory=")) {
-                       exc_given = 1;
-                       dir.exclude_per_dir = arg + 24;
-                       continue;
-               }
-               if (!strcmp(arg, "--exclude-standard")) {
-                       exc_given = 1;
-                       setup_standard_excludes(&dir);
-                       continue;
-               }
-               if (!strcmp(arg, "--full-name")) {
-                       prefix_offset = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--error-unmatch")) {
-                       error_unmatch = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--with-tree=")) {
-                       with_tree = arg + 12;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--abbrev=")) {
-                       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;
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
+                       ls_files_usage, 0);
+       if (show_tag || show_valid_bit) {
+               tag_cached = "H ";
+               tag_unmerged = "M ";
+               tag_removed = "R ";
+               tag_modified = "C ";
+               tag_other = "? ";
+               tag_killed = "K ";
+               tag_skip_worktree = "S ";
+               tag_resolve_undo = "U ";
        }
+       if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
+               require_work_tree = 1;
+       if (show_unmerged)
+               /*
+                * There's no point in showing unmerged unless
+                * you also show the stage information.
+                */
+               show_stage = 1;
+       if (dir.exclude_per_dir)
+               exc_given = 1;
 
        if (require_work_tree && !is_inside_work_tree())
                setup_work_tree();
 
-       pathspec = get_pathspec(prefix, argv + i);
+       pathspec = get_pathspec(prefix, argv);
 
-       /* be nice with submodule patsh ending in a slash */
-       read_cache();
+       /* be nice with submodule paths ending in a slash */
        if (pathspec)
                strip_trailing_slash_from_submodules();
 
@@ -543,15 +570,12 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                ps_matched = xcalloc(1, num);
        }
 
-       if (dir.show_ignored && !exc_given) {
-               fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
-                       argv[0]);
-               exit(1);
-       }
+       if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
+               die("ls-files --ignored needs some exclude pattern");
 
        /* With no flags, we default to showing the cached files */
        if (!(show_stage | show_deleted | show_others | show_unmerged |
-             show_killed | show_modified))
+             show_killed | show_modified | show_resolve_undo))
                show_cached = 1;
 
        if (prefix)
@@ -566,6 +590,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                overlay_tree_on_cache(with_tree, prefix);
        }
        show_files(&dir, prefix);
+       if (show_resolve_undo)
+               show_ru_info(prefix);
 
        if (ps_matched) {
                int bad;