git-commit: Allow partial commit of file removal.
[gitweb.git] / builtin-ls-files.c
index 61577ea13ffdf0143dcb8a13c37ae49c1f8f2018..6c1db86e8056285cb25359d55f5ebf9dd0e6f489 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 = "";
@@ -247,6 +249,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);
                }
        }
@@ -332,6 +336,67 @@ static const char *verify_pathspec(const char *prefix)
        return real_prefix;
 }
 
+/*
+ * 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);
+               }
+       }
+}
+
 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>] "
@@ -452,6 +517,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,9 +538,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                break;
        }
 
-       if (require_work_tree &&
-                       (!is_inside_work_tree() || is_inside_git_dir()))
-               die("This operation must be run in a work tree");
+       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");
+       }
 
        pathspec = get_pathspec(prefix, argv + i);
 
@@ -501,6 +572,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) {
@@ -509,8 +589,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++;