Code

receive-pack: allow deletion of corrupt refs
[git.git] / builtin-ls-files.c
index d36181a75541df10c5dc1595ccca5a6fc429591f..7f607098305fcf14c3e2a7634b6a5dc118445306 100644 (file)
@@ -9,6 +9,7 @@
 #include "quote.h"
 #include "dir.h"
 #include "builtin.h"
+#include "tree.h"
 
 static int abbrev;
 static int show_deleted;
@@ -26,6 +27,7 @@ static int prefix_offset;
 static const char **pathspec;
 static int error_unmatch;
 static char *ps_matched;
+static const char *with_tree;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -82,8 +84,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
                return;
 
        fputs(tag, stdout);
-       write_name_quoted("", 0, ent->name + offset, line_terminator, stdout);
-       putchar(line_terminator);
+       write_name_quoted(ent->name + offset, stdout, line_terminator);
 }
 
 static void show_other_files(struct dir_struct *dir)
@@ -206,21 +207,15 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
 
        if (!show_stage) {
                fputs(tag, stdout);
-               write_name_quoted("", 0, ce->name + offset,
-                                 line_terminator, stdout);
-               putchar(line_terminator);
-       }
-       else {
+       } else {
                printf("%s%06o %s %d\t",
                       tag,
                       ntohl(ce->ce_mode),
                       abbrev ? find_unique_abbrev(ce->sha1,abbrev)
                                : sha1_to_hex(ce->sha1),
                       ce_stage(ce));
-               write_name_quoted("", 0, ce->name + offset,
-                                 line_terminator, stdout);
-               putchar(line_terminator);
        }
+       write_name_quoted(ce->name + offset, stdout, line_terminator);
 }
 
 static void show_files(struct dir_struct *dir, const char *prefix)
@@ -247,6 +242,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
+                       if (ce->ce_flags & htons(CE_UPDATE))
+                               continue;
                        show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
                }
        }
@@ -276,7 +273,8 @@ static void prune_cache(const char *prefix)
 
        if (pos < 0)
                pos = -pos-1;
-       active_cache += pos;
+       memmove(active_cache, active_cache + pos,
+               (active_nr - pos) * sizeof(struct cache_entry *));
        active_nr -= pos;
        first = 0;
        last = active_nr;
@@ -295,7 +293,6 @@ static void prune_cache(const char *prefix)
 static const char *verify_pathspec(const char *prefix)
 {
        const char **p, *n, *prev;
-       char *real_prefix;
        unsigned long max;
 
        prev = NULL;
@@ -322,21 +319,76 @@ static const char *verify_pathspec(const char *prefix)
        if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
                die("git-ls-files: cannot generate relative filenames containing '..'");
 
-       real_prefix = NULL;
        prefix_len = max;
-       if (max) {
-               real_prefix = xmalloc(max + 1);
-               memcpy(real_prefix, prev, max);
-               real_prefix[max] = 0;
+       return max ? xmemdupz(prev, max) : NULL;
+}
+
+/*
+ * Read the tree specified with --with-tree option
+ * (typically, HEAD) into stage #1 and then
+ * squash them down to stage #0.  This is used for
+ * --error-unmatch to list and check the path patterns
+ * that were given from the command line.  We are not
+ * going to write this index out.
+ */
+static void overlay_tree(const char *tree_name, const char *prefix)
+{
+       struct tree *tree;
+       unsigned char sha1[20];
+       const char **match;
+       struct cache_entry *last_stage0 = NULL;
+       int i;
+
+       if (get_sha1(tree_name, sha1))
+               die("tree-ish %s not found.", tree_name);
+       tree = parse_tree_indirect(sha1);
+       if (!tree)
+               die("bad tree-ish %s", tree_name);
+
+       /* Hoist the unmerged entries up to stage #3 to make room */
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= htons(CE_STAGEMASK);
+       }
+
+       if (prefix) {
+               static const char *(matchbuf[2]);
+               matchbuf[0] = prefix;
+               matchbuf [1] = NULL;
+               match = matchbuf;
+       } else
+               match = NULL;
+       if (read_tree(tree, 1, match))
+               die("unable to read tree entries %s", tree_name);
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               switch (ce_stage(ce)) {
+               case 0:
+                       last_stage0 = ce;
+                       /* fallthru */
+               default:
+                       continue;
+               case 1:
+                       /*
+                        * If there is stage #0 entry for this, we do not
+                        * need to show it.  We use CE_UPDATE bit to mark
+                        * such an entry.
+                        */
+                       if (last_stage0 &&
+                           !strcmp(last_stage0->name, ce->name))
+                               ce->ce_flags |= htons(CE_UPDATE);
+               }
        }
-       return real_prefix;
 }
 
 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> ] [--full-name] [--abbrev] "
-       "[--] [<file>]*";
+       "[ --exclude-per-directory=<filename> ] [--exclude-standard] "
+       "[--full-name] [--abbrev] [--] [<file>]*";
 
 int cmd_ls_files(int argc, const char **argv, const char *prefix)
 {
@@ -444,6 +496,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                        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;
@@ -452,6 +509,10 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                        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)
@@ -469,11 +530,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                break;
        }
 
-       if (require_work_tree && !is_inside_work_tree()) {
-               const char *work_tree = get_git_work_tree();
-               if (!work_tree || chdir(work_tree))
-                       die("This operation must be run in a work tree");
-       }
+       if (require_work_tree && !is_inside_work_tree())
+               setup_work_tree();
 
        pathspec = get_pathspec(prefix, argv + i);
 
@@ -503,6 +561,15 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
        read_cache();
        if (prefix)
                prune_cache(prefix);
+       if (with_tree) {
+               /*
+                * Basic sanity check; show-stages and show-unmerged
+                * would not make any sense with this option.
+                */
+               if (show_stage || show_unmerged)
+                       die("ls-files --with-tree is incompatible with -s or -u");
+               overlay_tree(with_tree, prefix);
+       }
        show_files(&dir, prefix);
 
        if (ps_matched) {
@@ -511,8 +578,28 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                 */
                int num, errors = 0;
                for (num = 0; pathspec[num]; num++) {
+                       int other, found_dup;
+
                        if (ps_matched[num])
                                continue;
+                       /*
+                        * The caller might have fed identical pathspec
+                        * twice.  Do not barf on such a mistake.
+                        */
+                       for (found_dup = other = 0;
+                            !found_dup && pathspec[other];
+                            other++) {
+                               if (other == num || !ps_matched[other])
+                                       continue;
+                               if (!strcmp(pathspec[other], pathspec[num]))
+                                       /*
+                                        * Ok, we have a match already.
+                                        */
+                                       found_dup = 1;
+                       }
+                       if (found_dup)
+                               continue;
+
                        error("pathspec '%s' did not match any file(s) known to git.",
                              pathspec[num] + prefix_offset);
                        errors++;