Merge branch 'np/delta' into next
[gitweb.git] / ls-tree.c
index dd642e0bf059cdf969d370fc1155fb189a0c89ce..d005643ee08e03be2309c02e72522c6a81e8a1c6 100644 (file)
--- a/ls-tree.c
+++ b/ls-tree.c
 #include "cache.h"
 #include "blob.h"
 #include "tree.h"
+#include "quote.h"
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
 #define LS_TREE_ONLY 2
+#define LS_SHOW_TREES 4
+#define LS_NAME_ONLY 8
 static int ls_options = 0;
+const char **pathspec;
+static int chomp_prefix = 0;
+static const char *prefix;
 
-static struct tree_entry_list root_entry;
-
-static void prepare_root(unsigned char *sha1)
-{
-       unsigned char rsha[20];
-       unsigned long size;
-       void *buf;
-       struct tree *root_tree;
-
-       buf = read_object_with_reference(sha1, "tree", &size, rsha);
-       free(buf);
-       if (!buf)
-               die("Could not read %s", sha1_to_hex(sha1));
-
-       root_tree = lookup_tree(rsha);
-       if (!root_tree)
-               die("Could not read %s", sha1_to_hex(sha1));
-
-       /* Prepare a fake entry */
-       root_entry.directory = 1;
-       root_entry.executable = root_entry.symlink = 0;
-       root_entry.mode = S_IFDIR;
-       root_entry.name = "";
-       root_entry.item.tree = root_tree;
-       root_entry.parent = NULL;
-}
+static const char ls_tree_usage[] =
+       "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] <tree-ish> [path...]";
 
-static int prepare_children(struct tree_entry_list *elem)
+static int show_recursive(const char *base, int baselen, const char *pathname)
 {
-       if (!elem->directory)
-               return -1;
-       if (!elem->item.tree->object.parsed) {
-               struct tree_entry_list *e;
-               if (parse_tree(elem->item.tree))
-                       return -1;
-               /* Set up the parent link */
-               for (e = elem->item.tree->entries; e; e = e->next)
-                       e->parent = elem;
+       const char **s;
+
+       if (ls_options & LS_RECURSIVE)
+               return 1;
+
+       s = pathspec;
+       if (!s)
+               return 0;
+
+       for (;;) {
+               const char *spec = *s++;
+               int len, speclen;
+
+               if (!spec)
+                       return 0;
+               if (strncmp(base, spec, baselen))
+                       continue;
+               len = strlen(pathname);
+               spec += baselen;
+               speclen = strlen(spec);
+               if (speclen <= len)
+                       continue;
+               if (memcmp(pathname, spec, len))
+                       continue;
+               return 1;
        }
-       return 0;
 }
 
-static struct tree_entry_list *find_entry(const char *path, char *pathbuf)
+static int show_tree(unsigned char *sha1, const char *base, int baselen,
+                    const char *pathname, unsigned mode, int stage)
 {
-       const char *next, *slash;
-       int len;
-       struct tree_entry_list *elem = &root_entry, *oldelem = NULL;
-
-       *(pathbuf) = '\0';
-
-       /* Find tree element, descending from root, that
-        * corresponds to the named path, lazily expanding
-        * the tree if possible.
-        */
-
-       while (path) {
-               /* The fact we still have path means that the caller
-                * wants us to make sure that elem at this point is a
-                * directory, and possibly descend into it.  Even what
-                * is left is just trailing slashes, we loop back to
-                * here, and this call to prepare_children() will
-                * catch elem not being a tree.  Nice.
-                */
-               if (prepare_children(elem))
-                       return NULL;
-
-               slash = strchr(path, '/');
-               if (!slash) {
-                       len = strlen(path);
-                       next = NULL;
-               }
-               else {
-                       next = slash + 1;
-                       len = slash - path;
+       int retval = 0;
+       const char *type = "blob";
+
+       if (S_ISDIR(mode)) {
+               if (show_recursive(base, baselen, pathname)) {
+                       retval = READ_TREE_RECURSIVE;
+                       if (!(ls_options & LS_SHOW_TREES))
+                               return retval;
                }
-               if (len) {
-                       if (oldelem) {
-                               pathbuf += sprintf(pathbuf, "%s/", oldelem->name);
-                       }
-
-                       /* (len == 0) if the original path was "drivers/char/"
-                        * and we have run already two rounds, having elem
-                        * pointing at the drivers/char directory.
-                        */
-                       elem = elem->item.tree->entries;
-                       while (elem) {
-                               if ((strlen(elem->name) == len) &&
-                                   !strncmp(elem->name, path, len)) {
-                                       /* found */
-                                       break;
-                               }
-                               elem = elem->next;
-                       }
-                       if (!elem)
-                               return NULL;
-
-                       oldelem = elem;
-               }
-               path = next;
-       }
-
-       return elem;
-}
-
-static const char *entry_type(struct tree_entry_list *e)
-{
-       return (e->directory ? "tree" : "blob");
-}
-
-static const char *entry_hex(struct tree_entry_list *e)
-{
-       return sha1_to_hex(e->directory
-                          ? e->item.tree->object.sha1
-                          : e->item.blob->object.sha1);
-}
-
-/* forward declaration for mutually recursive routines */
-static int show_entry(struct tree_entry_list *, int, char *pathbuf);
-
-static int show_children(struct tree_entry_list *e, int level, char *pathbuf)
-{
-       int oldlen = strlen(pathbuf);
-
-       if (e != &root_entry)
-               sprintf(pathbuf + oldlen, "%s/", e->name);
-
-       if (prepare_children(e))
-               die("internal error: ls-tree show_children called with non tree");
-       e = e->item.tree->entries;
-       while (e) {
-               show_entry(e, level, pathbuf);
-               e = e->next;
-       }
-
-       pathbuf[oldlen] = '\0';
-
-       return 0;
-}
-
-static int show_entry(struct tree_entry_list *e, int level, char *pathbuf)
-{
-       int err = 0; 
-
-       if (e != &root_entry) {
-               printf("%06o %s %s      %s%s", e->mode, entry_type(e),
-                      entry_hex(e), pathbuf, e->name);
-               putchar(line_termination);
-       }
-
-       if (e->directory) {
-               /* If this is a directory, we have the following cases:
-                * (1) This is the top-level request (explicit path from the
-                *     command line, or "root" if there is no command line).
-                *  a. Without any flag.  We show direct children.  We do not 
-                *     recurse into them.
-                *  b. With -r.  We do recurse into children.
-                *  c. With -d.  We do not recurse into children.
-                * (2) We came here because our caller is either (1-a) or
-                *     (1-b).
-                *  a. Without any flag.  We do not show our children (which
-                *     are grandchildren for the original request).
-                *  b. With -r.  We continue to recurse into our children.
-                *  c. With -d.  We should not have come here to begin with.
-                */
-               if (level == 0 && !(ls_options & LS_TREE_ONLY))
-                       /* case (1)-a and (1)-b */
-                       err = err | show_children(e, level+1, pathbuf);
-               else if (level && ls_options & LS_RECURSIVE)
-                       /* case (2)-b */
-                       err = err | show_children(e, level+1, pathbuf);
-       }
-       return err;
-}
-
-static int list_one(const char *path)
-{
-       int err = 0;
-       char pathbuf[MAXPATHLEN + 1];
-       struct tree_entry_list *e = find_entry(path, pathbuf);
-       if (!e) {
-               /* traditionally ls-tree does not complain about
-                * missing path.  We may change this later to match
-                * what "/bin/ls -a" does, which is to complain.
-                */
-               return err;
+               type = "tree";
        }
-       err = err | show_entry(e, 0, pathbuf);
-       return err;
-}
-
-static int list(char **path)
-{
-       int i;
-       int err = 0;
-       for (i = 0; path[i]; i++)
-               err = err | list_one(path[i]);
-       return err;
+       else if (ls_options & LS_TREE_ONLY)
+               return 0;
+
+       if (chomp_prefix &&
+           (baselen < chomp_prefix || memcmp(prefix, base, chomp_prefix)))
+               return 0;
+
+       if (!(ls_options & LS_NAME_ONLY))
+               printf("%06o %s %s\t", mode, type, sha1_to_hex(sha1));
+       write_name_quoted(base + chomp_prefix, baselen - chomp_prefix,
+                         pathname,
+                         line_termination, stdout);
+       putchar(line_termination);
+       return retval;
 }
 
-static const char ls_tree_usage[] =
-       "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
-
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
-       static char *path0[] = { "", NULL };
-       char **path;
        unsigned char sha1[20];
+       struct tree *tree;
 
+       prefix = setup_git_directory();
+       if (prefix && *prefix)
+               chomp_prefix = strlen(prefix);
        while (1 < argc && argv[1][0] == '-') {
                switch (argv[1][1]) {
                case 'z':
@@ -231,20 +100,40 @@ int main(int argc, char **argv)
                case 'd':
                        ls_options |= LS_TREE_ONLY;
                        break;
+               case 't':
+                       ls_options |= LS_SHOW_TREES;
+                       break;
+               case '-':
+                       if (!strcmp(argv[1]+2, "name-only") ||
+                           !strcmp(argv[1]+2, "name-status")) {
+                               ls_options |= LS_NAME_ONLY;
+                               break;
+                       }
+                       if (!strcmp(argv[1]+2, "full-name")) {
+                               chomp_prefix = 0;
+                               break;
+                       }
+                       /* otherwise fallthru */
                default:
                        usage(ls_tree_usage);
                }
                argc--; argv++;
        }
+       /* -d -r should imply -t, but -d by itself should not have to. */
+       if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
+           ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
+               ls_options |= LS_SHOW_TREES;
 
        if (argc < 2)
                usage(ls_tree_usage);
        if (get_sha1(argv[1], sha1) < 0)
                usage(ls_tree_usage);
 
-       path = (argc == 2) ? path0 : (argv + 2);
-       prepare_root(sha1);
-       if (list(path) < 0)
-               die("list failed");
+       pathspec = get_pathspec(prefix, argv + 2);
+       tree = parse_tree_indirect(sha1);
+       if (!tree)
+               die("not a tree object");
+       read_tree_recursive(tree, "", 0, 0, pathspec, show_tree);
+
        return 0;
 }