]> git.proxmox.com Git - libgit2.git/blobdiff - src/iterator.c
Improvements to ignore performance on Windows.
[libgit2.git] / src / iterator.c
index 63c14f96266c95e1372a1f4c9432a1ef982bc690..8bab1aab0ac7426f126e72d113be1c16e8409175 100644 (file)
@@ -336,7 +336,7 @@ static int tree_iterator__push_frame(tree_iterator *ti)
 {
        int error = 0;
        tree_iterator_frame *head = ti->head, *tf = NULL;
-       size_t i, n_entries = 0;
+       size_t i, n_entries = 0, alloclen;
 
        if (head->current >= head->n_entries || !head->entries[head->current]->tree)
                return GIT_ITEROVER;
@@ -344,8 +344,10 @@ static int tree_iterator__push_frame(tree_iterator *ti)
        for (i = head->current; i < head->next; ++i)
                n_entries += git_tree_entrycount(head->entries[i]->tree);
 
-       tf = git__calloc(sizeof(tree_iterator_frame) +
-               n_entries * sizeof(tree_iterator_entry *), 1);
+       GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, sizeof(tree_iterator_entry *), n_entries);
+       GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, sizeof(tree_iterator_frame));
+
+       tf = git__calloc(1, alloclen);
        GITERR_CHECK_ALLOC(tf);
 
        tf->n_entries = n_entries;
@@ -897,6 +899,7 @@ struct fs_iterator_frame {
        fs_iterator_frame *next;
        git_vector entries;
        size_t index;
+       int is_ignored;
 };
 
 typedef struct fs_iterator fs_iterator;
@@ -1016,6 +1019,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
                fs_iterator__free_frame(ff);
                return GIT_ENOTFOUND;
        }
+       fi->base.stat_calls += ff->entries.length;
 
        fs_iterator__seek_frame_start(fi, ff);
 
@@ -1266,6 +1270,16 @@ typedef struct {
        fs_iterator fi;
        git_ignores ignores;
        int is_ignored;
+
+       /*
+        * We may have a tree or the index+snapshot to compare against
+        * when checking for submodules.
+        */
+       git_tree *tree;
+       git_index *index;
+       git_vector index_snapshot;
+       git_vector_cmp entry_srch;
+
 } workdir_iterator;
 
 GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path)
@@ -1287,26 +1301,93 @@ GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path)
        return (len == 4 || path->ptr[len - 5] == '/');
 }
 
+/**
+ * Figure out if an entry is a submodule.
+ *
+ * We consider it a submodule if the path is listed as a submodule in
+ * either the tree or the index.
+ */
+static int is_submodule(workdir_iterator *wi, git_path_with_stat *ie)
+{
+       int error, is_submodule = 0;
+
+       if (wi->tree) {
+               git_tree_entry *e;
+
+               /* remove the trailing slash for finding */
+               ie->path[ie->path_len-1] = '\0';
+               error = git_tree_entry_bypath(&e, wi->tree, ie->path);
+               ie->path[ie->path_len-1] = '/';
+               if (error < 0 && error != GIT_ENOTFOUND)
+                       return 0;
+               if (!error) {
+                       is_submodule = e->attr == GIT_FILEMODE_COMMIT;
+                       git_tree_entry_free(e);
+               }
+       }
+
+       if (!is_submodule && wi->index) {
+               git_index_entry *e;
+               size_t pos;
+
+               error = git_index_snapshot_find(&pos, &wi->index_snapshot, wi->entry_srch, ie->path, ie->path_len-1, 0);
+               if (error < 0 && error != GIT_ENOTFOUND)
+                       return 0;
+
+               if (!error) {
+                       e = git_vector_get(&wi->index_snapshot, pos);
+
+                       is_submodule = e->mode == GIT_FILEMODE_COMMIT;
+               }
+       }
+
+       return is_submodule;
+}
+
+GIT_INLINE(git_dir_flag) git_entry__dir_flag(git_index_entry *entry) {
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+       return (entry && entry->mode)
+               ? S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE
+               : GIT_DIR_FLAG_UNKNOWN;
+#else
+       return GIT_DIR_FLAG_UNKNOWN;
+#endif
+}
+
 static int workdir_iterator__enter_dir(fs_iterator *fi)
 {
+       workdir_iterator *wi = (workdir_iterator *)fi;
        fs_iterator_frame *ff = fi->stack;
        size_t pos;
        git_path_with_stat *entry;
        bool found_submodules = false;
 
-       /* only push new ignores if this is not top level directory */
+       git_dir_flag dir_flag = git_entry__dir_flag(&fi->entry);
+
+       /* check if this directory is ignored */
+       if (git_ignore__lookup(&ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len, dir_flag) < 0) {
+               giterr_clear();
+               ff->is_ignored = GIT_IGNORE_NOTFOUND;
+       }
+
+       /* if this is not the top level directory... */
        if (ff->next != NULL) {
-               workdir_iterator *wi = (workdir_iterator *)fi;
                ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/');
 
+               /* inherit ignored from parent if no rule specified */
+               if (ff->is_ignored <= GIT_IGNORE_NOTFOUND)
+                       ff->is_ignored = ff->next->is_ignored;
+
+               /* push new ignores for files in this directory */
                (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]);
        }
 
        /* convert submodules to GITLINK and remove trailing slashes */
        git_vector_foreach(&ff->entries, pos, entry) {
-               if (S_ISDIR(entry->st.st_mode) &&
-                       git_submodule__is_submodule(fi->base.repo, entry->path))
-               {
+               if (!S_ISDIR(entry->st.st_mode) || !strcmp(GIT_DIR, entry->path))
+                       continue;
+
+               if (is_submodule(wi, entry)) {
                        entry->st.st_mode = GIT_FILEMODE_COMMIT;
                        entry->path_len--;
                        entry->path[entry->path_len] = '\0';
@@ -1340,7 +1421,7 @@ static int workdir_iterator__update_entry(fs_iterator *fi)
                return GIT_ENOTFOUND;
 
        /* reset is_ignored since we haven't checked yet */
-       wi->is_ignored = -1;
+       wi->is_ignored = GIT_IGNORE_UNCHECKED;
 
        return 0;
 }
@@ -1348,6 +1429,9 @@ static int workdir_iterator__update_entry(fs_iterator *fi)
 static void workdir_iterator__free(git_iterator *self)
 {
        workdir_iterator *wi = (workdir_iterator *)self;
+       if (wi->index)
+               git_index_snapshot_release(&wi->index_snapshot, wi->index);
+       git_tree_free(wi->tree);
        fs_iterator__free(self);
        git_ignore__free(&wi->ignores);
 }
@@ -1356,6 +1440,8 @@ int git_iterator_for_workdir_ext(
        git_iterator **out,
        git_repository *repo,
        const char *repo_workdir,
+       git_index *index,
+       git_tree *tree,
        git_iterator_flag_t flags,
        const char *start,
        const char *end)
@@ -1387,6 +1473,18 @@ int git_iterator_for_workdir_ext(
                return error;
        }
 
+       if (tree && (error = git_object_dup((git_object **)&wi->tree, (git_object *)tree)) < 0)
+               return error;
+
+       wi->index = index;
+       if (index && (error = git_index_snapshot_new(&wi->index_snapshot, index)) < 0) {
+               git_iterator_free((git_iterator *)wi);
+               return error;
+       }
+       wi->entry_srch = iterator__ignore_case(wi) ?
+               git_index_entry_isrch : git_index_entry_srch;
+
+
        /* try to look up precompose and set flag if appropriate */
        if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
                giterr_clear();
@@ -1396,7 +1494,6 @@ int git_iterator_for_workdir_ext(
        return fs_iterator__initialize(out, &wi->fi, repo_workdir);
 }
 
-
 void git_iterator_free(git_iterator *iter)
 {
        if (iter == NULL)
@@ -1485,6 +1582,20 @@ int git_iterator_current_parent_tree(
        return 0;
 }
 
+static void workdir_iterator_update_is_ignored(workdir_iterator *wi)
+{
+       git_dir_flag dir_flag = git_entry__dir_flag(&wi->fi.entry);
+
+       if (git_ignore__lookup(&wi->is_ignored, &wi->ignores, wi->fi.entry.path, dir_flag) < 0) {
+               giterr_clear();
+               wi->is_ignored = GIT_IGNORE_NOTFOUND;
+       }
+
+       /* use ignore from containing frame stack */
+       if (wi->is_ignored <= GIT_IGNORE_NOTFOUND)
+               wi->is_ignored = wi->fi.stack->is_ignored;
+}
+
 bool git_iterator_current_is_ignored(git_iterator *iter)
 {
        workdir_iterator *wi = (workdir_iterator *)iter;
@@ -1492,14 +1603,22 @@ bool git_iterator_current_is_ignored(git_iterator *iter)
        if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
                return false;
 
-       if (wi->is_ignored != -1)
-               return (bool)(wi->is_ignored != 0);
+       if (wi->is_ignored != GIT_IGNORE_UNCHECKED)
+               return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
 
-       if (git_ignore__lookup(
-                       &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0)
-               wi->is_ignored = true;
+       workdir_iterator_update_is_ignored(wi);
 
-       return (bool)wi->is_ignored;
+       return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
+}
+
+bool git_iterator_current_tree_is_ignored(git_iterator *iter)
+{
+       workdir_iterator *wi = (workdir_iterator *)iter;
+
+       if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+               return false;
+
+       return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE);
 }
 
 int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
@@ -1528,3 +1647,73 @@ int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
 
        return 0;
 }
+
+int git_iterator_advance_over_with_status(
+       const git_index_entry **entryptr,
+       git_iterator_status_t *status,
+       git_iterator *iter)
+{
+       int error = 0;
+       workdir_iterator *wi = (workdir_iterator *)iter;
+       char *base = NULL;
+       const git_index_entry *entry;
+
+       *status = GIT_ITERATOR_STATUS_NORMAL;
+
+       if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+               return git_iterator_advance(entryptr, iter);
+       if ((error = git_iterator_current(&entry, iter)) < 0)
+               return error;
+
+       if (!S_ISDIR(entry->mode)) {
+               workdir_iterator_update_is_ignored(wi);
+               if (wi->is_ignored == GIT_IGNORE_TRUE)
+                       *status = GIT_ITERATOR_STATUS_IGNORED;
+               return git_iterator_advance(entryptr, iter);
+       }
+
+       *status = GIT_ITERATOR_STATUS_EMPTY;
+
+       base = git__strdup(entry->path);
+       GITERR_CHECK_ALLOC(base);
+
+       /* scan inside directory looking for a non-ignored item */
+       while (entry && !iter->prefixcomp(entry->path, base)) {
+               workdir_iterator_update_is_ignored(wi);
+
+               /* if we found an explicitly ignored item, then update from
+                * EMPTY to IGNORED
+                */
+               if (wi->is_ignored == GIT_IGNORE_TRUE)
+                       *status = GIT_ITERATOR_STATUS_IGNORED;
+               else if (S_ISDIR(entry->mode)) {
+                       error = git_iterator_advance_into(&entry, iter);
+
+                       if (!error)
+                               continue;
+                       else if (error == GIT_ENOTFOUND) {
+                               error = 0;
+                               wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */
+                       } else
+                               break; /* real error, stop here */
+               } else {
+                       /* we found a non-ignored item, treat parent as untracked */
+                       *status = GIT_ITERATOR_STATUS_NORMAL;
+                       break;
+               }
+
+               if ((error = git_iterator_advance(&entry, iter)) < 0)
+                       break;
+       }
+
+       /* wrap up scan back to base directory */
+       while (entry && !iter->prefixcomp(entry->path, base))
+               if ((error = git_iterator_advance(&entry, iter)) < 0)
+                       break;
+
+       *entryptr = entry;
+       git__free(base);
+
+       return error;
+}
+