Allow GIT_DIR to be an absolute path
[git.git] / ls-tree.c
index d2a57d0..d9f15e3 100644 (file)
--- a/ls-tree.c
+++ b/ls-tree.c
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "blob.h"
 #include "tree.h"
+#include "quote.h"
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
@@ -54,67 +55,66 @@ static int prepare_children(struct tree_entry_list *elem)
        return 0;
 }
 
-static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem,
-                                           const char *path,
-                                           const char *path_end)
+static struct tree_entry_list *find_entry(const char *path, char *pathbuf)
 {
-       const char *ep;
+       const char *next, *slash;
        int len;
+       struct tree_entry_list *elem = &root_entry, *oldelem = NULL;
 
-       while (path < path_end) {
-               if (prepare_children(elem))
-                       return NULL;
-
-               /* In elem->tree->entries, find the one that has name
-                * that matches what is between path and ep.
-                */
-               elem = elem->item.tree->entries;
-
-               ep = strchr(path, '/');
-               if (!ep || path_end <= ep)
-                       ep = path_end;
-               len = ep - path;
-
-               while (elem) {
-                       if ((strlen(elem->name) == len) &&
-                           !strncmp(elem->name, path, len))
-                               break;
-                       elem = elem->next;
-               }
-               if (path_end <= ep || !elem)
-                       return elem;
-               while (*ep == '/' && ep < path_end)
-                       ep++;
-               path = ep;
-       }
-       return NULL;
-}
+       *(pathbuf) = '\0';
 
-static struct tree_entry_list *find_entry(const char *path,
-                                         const char *path_end)
-{
        /* Find tree element, descending from root, that
         * corresponds to the named path, lazily expanding
         * the tree if possible.
         */
-       if (path == path_end) {
-               /* Special.  This is the root level */
-               return &root_entry;
-       }
-       return find_entry_0(&root_entry, path, path_end);
-}
 
-static void show_entry_name(struct tree_entry_list *e)
-{
-       /* This is yucky.  The root level is there for
-        * our convenience but we really want to do a
-        * forest.
-        */
-       if (e->parent && e->parent != &root_entry) {
-               show_entry_name(e->parent);
-               putchar('/');
+       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;
+               }
+               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;
        }
-       printf("%s", e->name);
+
+       return elem;
 }
 
 static const char *entry_type(struct tree_entry_list *e)
@@ -130,28 +130,36 @@ static const char *entry_hex(struct tree_entry_list *e)
 }
 
 /* forward declaration for mutually recursive routines */
-static int show_entry(struct tree_entry_list *, int);
+static int show_entry(struct tree_entry_list *, int, char *pathbuf);
 
-static int show_children(struct tree_entry_list *e, int level)
+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);
+               show_entry(e, level, pathbuf);
                e = e->next;
        }
+
+       pathbuf[oldlen] = '\0';
+
        return 0;
 }
 
-static int show_entry(struct tree_entry_list *e, int level)
+static int show_entry(struct tree_entry_list *e, int level, char *pathbuf)
 {
        int err = 0; 
 
        if (e != &root_entry) {
-               printf("%06o %s %s      ", e->mode, entry_type(e),
-                      entry_hex(e));
-               show_entry_name(e);
+               printf("%06o %s %s      ",
+                      e->mode, entry_type(e), entry_hex(e));
+               write_name_quoted(pathbuf, e->name, line_termination, stdout);
                putchar(line_termination);
        }
 
@@ -172,18 +180,19 @@ static int show_entry(struct tree_entry_list *e, int level)
                 */
                if (level == 0 && !(ls_options & LS_TREE_ONLY))
                        /* case (1)-a and (1)-b */
-                       err = err | show_children(e, level+1);
+                       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);
+                       err = err | show_children(e, level+1, pathbuf);
        }
        return err;
 }
 
-static int list_one(const char *path, const char *path_end)
+static int list_one(const char *path)
 {
        int err = 0;
-       struct tree_entry_list *e = find_entry(path, path_end);
+       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
@@ -191,7 +200,7 @@ static int list_one(const char *path, const char *path_end)
                 */
                return err;
        }
-       err = err | show_entry(e, 0);
+       err = err | show_entry(e, 0, pathbuf);
        return err;
 }
 
@@ -199,16 +208,12 @@ static int list(char **path)
 {
        int i;
        int err = 0;
-       for (i = 0; path[i]; i++) {
-               int len = strlen(path[i]);
-               while (0 <= len && path[i][len] == '/')
-                       len--;
-               err = err | list_one(path[i], path[i] + len);
-       }
+       for (i = 0; path[i]; i++)
+               err = err | list_one(path[i]);
        return err;
 }
 
-static const char *ls_tree_usage =
+static const char ls_tree_usage[] =
        "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
 
 int main(int argc, char **argv)