Merge branch 'nd/resolve-ref'
[gitweb.git] / tree-walk.c
index 67a9a0c5a5bf7d7125765679318cfcd68c160da7..f82dba6a1f43bf2a259952a4fd6db94d6335deb7 100644 (file)
@@ -1,5 +1,7 @@
 #include "cache.h"
 #include "tree-walk.h"
+#include "unpack-trees.h"
+#include "dir.h"
 #include "tree.h"
 
 static const char *get_mode(const char *str, unsigned int *modep)
@@ -114,7 +116,7 @@ void setup_traverse_info(struct traverse_info *info, const char *base)
 
 char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n)
 {
-       int len = tree_entry_len(n->path, n->sha1);
+       int len = tree_entry_len(n);
        int pathlen = info->pathlen;
 
        path[pathlen + len] = 0;
@@ -124,7 +126,7 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str
                        break;
                path[--pathlen] = '/';
                n = &info->name;
-               len = tree_entry_len(n->path, n->sha1);
+               len = tree_entry_len(n);
                info = info->prev;
                pathlen -= len;
        }
@@ -251,7 +253,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
         * The caller wants "first" from this tree, or nothing.
         */
        path = a->path;
-       len = tree_entry_len(a->path, a->sha1);
+       len = tree_entry_len(a);
        switch (check_entry_match(first, first_len, path, len)) {
        case -1:
                entry_clear(a);
@@ -269,7 +271,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
        while (probe.size) {
                entry_extract(&probe, a);
                path = a->path;
-               len = tree_entry_len(a->path, a->sha1);
+               len = tree_entry_len(a);
                switch (check_entry_match(first, first_len, path, len)) {
                case -1:
                        entry_clear(a);
@@ -307,21 +309,42 @@ static void free_extended_entry(struct tree_desc_x *t)
        }
 }
 
+static inline int prune_traversal(struct name_entry *e,
+                                 struct traverse_info *info,
+                                 struct strbuf *base,
+                                 int still_interesting)
+{
+       if (!info->pathspec || still_interesting == 2)
+               return 2;
+       if (still_interesting < 0)
+               return still_interesting;
+       return tree_entry_interesting(e, base, 0, info->pathspec);
+}
+
 int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
 {
        int ret = 0;
+       int error = 0;
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
        int i;
        struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
+       struct strbuf base = STRBUF_INIT;
+       int interesting = 1;
 
        for (i = 0; i < n; i++)
                tx[i].d = t[i];
 
+       if (info->prev) {
+               strbuf_grow(&base, info->pathlen);
+               make_traverse_path(base.buf, info->prev, &info->name);
+               base.buf[info->pathlen-1] = '/';
+               strbuf_setlen(&base, info->pathlen);
+       }
        for (;;) {
                unsigned long mask, dirmask;
                const char *first = NULL;
                int first_len = 0;
-               struct name_entry *e;
+               struct name_entry *e = NULL;
                int len;
 
                for (i = 0; i < n; i++) {
@@ -339,7 +362,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                        e = entry + i;
                        if (!e->path)
                                continue;
-                       len = tree_entry_len(e->path, e->sha1);
+                       len = tree_entry_len(e);
                        if (!first) {
                                first = e->path;
                                first_len = len;
@@ -358,7 +381,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                                /* Cull the ones that are not the earliest */
                                if (!e->path)
                                        continue;
-                               len = tree_entry_len(e->path, e->sha1);
+                               len = tree_entry_len(e);
                                if (name_compare(e->path, len, first, first_len))
                                        entry_clear(e);
                        }
@@ -373,13 +396,22 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                        mask |= 1ul << i;
                        if (S_ISDIR(entry[i].mode))
                                dirmask |= 1ul << i;
+                       e = &entry[i];
                }
                if (!mask)
                        break;
-               ret = info->fn(n, mask, dirmask, entry, info);
-               if (ret < 0)
+               interesting = prune_traversal(e, info, &base, interesting);
+               if (interesting < 0)
                        break;
-               mask &= ret;
+               if (interesting) {
+                       ret = info->fn(n, mask, dirmask, entry, info);
+                       if (ret < 0) {
+                               error = ret;
+                               if (!info->show_all_errors)
+                                       break;
+                       }
+                       mask &= ret;
+               }
                ret = 0;
                for (i = 0; i < n; i++)
                        if (mask & (1ul << i))
@@ -389,7 +421,8 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
        for (i = 0; i < n; i++)
                free_extended_entry(tx + i);
        free(tx);
-       return ret;
+       strbuf_release(&base);
+       return error;
 }
 
 static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
@@ -401,8 +434,8 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char
                int entrylen, cmp;
 
                sha1 = tree_entry_extract(t, &entry, mode);
+               entrylen = tree_entry_len(&t->entry);
                update_tree_entry(t);
-               entrylen = tree_entry_len(entry, sha1);
                if (entrylen > namelen)
                        continue;
                cmp = memcmp(name, entry, entrylen);
@@ -432,7 +465,6 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
        int retval;
        void *tree;
        unsigned long size;
-       struct tree_desc t;
        unsigned char root[20];
 
        tree = read_object_with_reference(tree_sha1, tree_type, &size, root);
@@ -445,8 +477,193 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
                return 0;
        }
 
-       init_tree_desc(&t, tree, size);
-       retval = find_tree_entry(&t, name, sha1, mode);
+       if (!size) {
+               retval = -1;
+       } else {
+               struct tree_desc t;
+               init_tree_desc(&t, tree, size);
+               retval = find_tree_entry(&t, name, sha1, mode);
+       }
        free(tree);
        return retval;
 }
+
+static int match_entry(const struct name_entry *entry, int pathlen,
+                      const char *match, int matchlen,
+                      int *never_interesting)
+{
+       int m = -1; /* signals that we haven't called strncmp() */
+
+       if (*never_interesting) {
+               /*
+                * We have not seen any match that sorts later
+                * than the current path.
+                */
+
+               /*
+                * Does match sort strictly earlier than path
+                * with their common parts?
+                */
+               m = strncmp(match, entry->path,
+                           (matchlen < pathlen) ? matchlen : pathlen);
+               if (m < 0)
+                       return 0;
+
+               /*
+                * If we come here even once, that means there is at
+                * least one pathspec that would sort equal to or
+                * later than the path we are currently looking at.
+                * In other words, if we have never reached this point
+                * after iterating all pathspecs, it means all
+                * pathspecs are either outside of base, or inside the
+                * base but sorts strictly earlier than the current
+                * one.  In either case, they will never match the
+                * subsequent entries.  In such a case, we initialized
+                * the variable to -1 and that is what will be
+                * returned, allowing the caller to terminate early.
+                */
+               *never_interesting = 0;
+       }
+
+       if (pathlen > matchlen)
+               return 0;
+
+       if (matchlen > pathlen) {
+               if (match[pathlen] != '/')
+                       return 0;
+               if (!S_ISDIR(entry->mode))
+                       return 0;
+       }
+
+       if (m == -1)
+               /*
+                * we cheated and did not do strncmp(), so we do
+                * that here.
+                */
+               m = strncmp(match, entry->path, pathlen);
+
+       /*
+        * If common part matched earlier then it is a hit,
+        * because we rejected the case where path is not a
+        * leading directory and is shorter than match.
+        */
+       if (!m)
+               return 1;
+
+       return 0;
+}
+
+static int match_dir_prefix(const char *base,
+                           const char *match, int matchlen)
+{
+       if (strncmp(base, match, matchlen))
+               return 0;
+
+       /*
+        * If the base is a subdirectory of a path which
+        * was specified, all of them are interesting.
+        */
+       if (!matchlen ||
+           base[matchlen] == '/' ||
+           match[matchlen - 1] == '/')
+               return 1;
+
+       /* Just a random prefix match */
+       return 0;
+}
+
+/*
+ * Is a tree entry interesting given the pathspec we have?
+ *
+ * Pre-condition: either baselen == base_offset (i.e. empty path)
+ * or base[baselen-1] == '/' (i.e. with trailing slash).
+ */
+enum interesting tree_entry_interesting(const struct name_entry *entry,
+                                       struct strbuf *base, int base_offset,
+                                       const struct pathspec *ps)
+{
+       int i;
+       int pathlen, baselen = base->len - base_offset;
+       int never_interesting = ps->has_wildcard ?
+               entry_not_interesting : all_entries_not_interesting;
+
+       if (!ps->nr) {
+               if (!ps->recursive || ps->max_depth == -1)
+                       return all_entries_interesting;
+               return within_depth(base->buf + base_offset, baselen,
+                                   !!S_ISDIR(entry->mode),
+                                   ps->max_depth) ?
+                       entry_interesting : entry_not_interesting;
+       }
+
+       pathlen = tree_entry_len(entry);
+
+       for (i = ps->nr - 1; i >= 0; i--) {
+               const struct pathspec_item *item = ps->items+i;
+               const char *match = item->match;
+               const char *base_str = base->buf + base_offset;
+               int matchlen = item->len;
+
+               if (baselen >= matchlen) {
+                       /* If it doesn't match, move along... */
+                       if (!match_dir_prefix(base_str, match, matchlen))
+                               goto match_wildcards;
+
+                       if (!ps->recursive || ps->max_depth == -1)
+                               return all_entries_interesting;
+
+                       return within_depth(base_str + matchlen + 1,
+                                           baselen - matchlen - 1,
+                                           !!S_ISDIR(entry->mode),
+                                           ps->max_depth) ?
+                               entry_interesting : entry_not_interesting;
+               }
+
+               /* Either there must be no base, or the base must match. */
+               if (baselen == 0 || !strncmp(base_str, match, baselen)) {
+                       if (match_entry(entry, pathlen,
+                                       match + baselen, matchlen - baselen,
+                                       &never_interesting))
+                               return entry_interesting;
+
+                       if (item->use_wildcard) {
+                               if (!fnmatch(match + baselen, entry->path, 0))
+                                       return entry_interesting;
+
+                               /*
+                                * Match all directories. We'll try to
+                                * match files later on.
+                                */
+                               if (ps->recursive && S_ISDIR(entry->mode))
+                                       return entry_interesting;
+                       }
+
+                       continue;
+               }
+
+match_wildcards:
+               if (!item->use_wildcard)
+                       continue;
+
+               /*
+                * Concatenate base and entry->path into one and do
+                * fnmatch() on it.
+                */
+
+               strbuf_add(base, entry->path, pathlen);
+
+               if (!fnmatch(match, base->buf + base_offset, 0)) {
+                       strbuf_setlen(base, base_offset + baselen);
+                       return entry_interesting;
+               }
+               strbuf_setlen(base, base_offset + baselen);
+
+               /*
+                * Match all directories. We'll try to match files
+                * later on.
+                */
+               if (ps->recursive && S_ISDIR(entry->mode))
+                       return entry_interesting;
+       }
+       return never_interesting; /* No matches */
+}