revision: use a prio_queue to hold rewritten parents
[gitweb.git] / revision.c
index 8f886fe8c644bfce221ddd2be94db7f05b5451e7..9265200b7a0c507da3bd008d3b7195560a44770f 100644 (file)
@@ -27,6 +27,7 @@
 #include "commit-reach.h"
 #include "commit-graph.h"
 #include "prio-queue.h"
+#include "hashmap.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -99,6 +100,148 @@ void mark_tree_uninteresting(struct repository *r, struct tree *tree)
        mark_tree_contents_uninteresting(r, tree);
 }
 
+struct path_and_oids_entry {
+       struct hashmap_entry ent;
+       char *path;
+       struct oidset trees;
+};
+
+static int path_and_oids_cmp(const void *hashmap_cmp_fn_data,
+                            const struct path_and_oids_entry *e1,
+                            const struct path_and_oids_entry *e2,
+                            const void *keydata)
+{
+       return strcmp(e1->path, e2->path);
+}
+
+static void paths_and_oids_init(struct hashmap *map)
+{
+       hashmap_init(map, (hashmap_cmp_fn) path_and_oids_cmp, NULL, 0);
+}
+
+static void paths_and_oids_clear(struct hashmap *map)
+{
+       struct hashmap_iter iter;
+       struct path_and_oids_entry *entry;
+       hashmap_iter_init(map, &iter);
+
+       while ((entry = (struct path_and_oids_entry *)hashmap_iter_next(&iter))) {
+               oidset_clear(&entry->trees);
+               free(entry->path);
+       }
+
+       hashmap_free(map, 1);
+}
+
+static void paths_and_oids_insert(struct hashmap *map,
+                                 const char *path,
+                                 const struct object_id *oid)
+{
+       int hash = strhash(path);
+       struct path_and_oids_entry key;
+       struct path_and_oids_entry *entry;
+
+       hashmap_entry_init(&key, hash);
+
+       /* use a shallow copy for the lookup */
+       key.path = (char *)path;
+       oidset_init(&key.trees, 0);
+
+       if (!(entry = (struct path_and_oids_entry *)hashmap_get(map, &key, NULL))) {
+               entry = xcalloc(1, sizeof(struct path_and_oids_entry));
+               hashmap_entry_init(entry, hash);
+               entry->path = xstrdup(key.path);
+               oidset_init(&entry->trees, 16);
+               hashmap_put(map, entry);
+       }
+
+       oidset_insert(&entry->trees, oid);
+}
+
+static void add_children_by_path(struct repository *r,
+                                struct tree *tree,
+                                struct hashmap *map)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+
+       if (!tree)
+               return;
+
+       if (parse_tree_gently(tree, 1) < 0)
+               return;
+
+       init_tree_desc(&desc, tree->buffer, tree->size);
+       while (tree_entry(&desc, &entry)) {
+               switch (object_type(entry.mode)) {
+               case OBJ_TREE:
+                       paths_and_oids_insert(map, entry.path, &entry.oid);
+
+                       if (tree->object.flags & UNINTERESTING) {
+                               struct tree *child = lookup_tree(r, &entry.oid);
+                               if (child)
+                                       child->object.flags |= UNINTERESTING;
+                       }
+                       break;
+               case OBJ_BLOB:
+                       if (tree->object.flags & UNINTERESTING) {
+                               struct blob *child = lookup_blob(r, &entry.oid);
+                               if (child)
+                                       child->object.flags |= UNINTERESTING;
+                       }
+                       break;
+               default:
+                       /* Subproject commit - not in this repository */
+                       break;
+               }
+       }
+
+       free_tree_buffer(tree);
+}
+
+void mark_trees_uninteresting_sparse(struct repository *r,
+                                    struct oidset *trees)
+{
+       unsigned has_interesting = 0, has_uninteresting = 0;
+       struct hashmap map;
+       struct hashmap_iter map_iter;
+       struct path_and_oids_entry *entry;
+       struct object_id *oid;
+       struct oidset_iter iter;
+
+       oidset_iter_init(trees, &iter);
+       while ((!has_interesting || !has_uninteresting) &&
+              (oid = oidset_iter_next(&iter))) {
+               struct tree *tree = lookup_tree(r, oid);
+
+               if (!tree)
+                       continue;
+
+               if (tree->object.flags & UNINTERESTING)
+                       has_uninteresting = 1;
+               else
+                       has_interesting = 1;
+       }
+
+       /* Do not walk unless we have both types of trees. */
+       if (!has_uninteresting || !has_interesting)
+               return;
+
+       paths_and_oids_init(&map);
+
+       oidset_iter_init(trees, &iter);
+       while ((oid = oidset_iter_next(&iter))) {
+               struct tree *tree = lookup_tree(r, oid);
+               add_children_by_path(r, tree, &map);
+       }
+
+       hashmap_iter_init(&map, &map_iter);
+       while ((entry = hashmap_iter_next(&map_iter)))
+               mark_trees_uninteresting_sparse(r, &entry->trees);
+
+       paths_and_oids_clear(&map);
+}
+
 struct commit_stack {
        struct commit **items;
        size_t nr, alloc;
@@ -768,26 +911,11 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                commit->object.flags |= TREESAME;
 }
 
-static void commit_list_insert_by_date_cached(struct commit *p, struct commit_list **head,
-                   struct commit_list *cached_base, struct commit_list **cache)
-{
-       struct commit_list *new_entry;
-
-       if (cached_base && p->date < cached_base->item->date)
-               new_entry = commit_list_insert_by_date(p, &cached_base->next);
-       else
-               new_entry = commit_list_insert_by_date(p, head);
-
-       if (cache && (!*cache || p->date < (*cache)->item->date))
-               *cache = new_entry;
-}
-
 static int process_parents(struct rev_info *revs, struct commit *commit,
-                          struct commit_list **list, struct commit_list **cache_ptr)
+                          struct commit_list **list, struct prio_queue *queue)
 {
        struct commit_list *parent = commit->parents;
        unsigned left_flag;
-       struct commit_list *cached_base = cache_ptr ? *cache_ptr : NULL;
 
        if (commit->object.flags & ADDED)
                return 0;
@@ -823,7 +951,9 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                                continue;
                        p->object.flags |= SEEN;
                        if (list)
-                               commit_list_insert_by_date_cached(p, list, cached_base, cache_ptr);
+                               commit_list_insert_by_date(p, list);
+                       if (queue)
+                               prio_queue_put(queue, p);
                }
                return 0;
        }
@@ -863,7 +993,9 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                if (!(p->object.flags & SEEN)) {
                        p->object.flags |= SEEN;
                        if (list)
-                               commit_list_insert_by_date_cached(p, list, cached_base, cache_ptr);
+                               commit_list_insert_by_date(p, list);
+                       if (queue)
+                               prio_queue_put(queue, p);
                }
                if (revs->first_parent_only)
                        break;
@@ -1397,7 +1529,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags)
 {
        struct worktree **worktrees, **p;
 
-       read_index(revs->repo->index);
+       repo_read_index(revs->repo);
        do_add_index_objects_to_pending(revs, revs->repo->index, flags);
 
        if (revs->single_worktree)
@@ -1544,7 +1676,7 @@ static void prepare_show_merge(struct rev_info *revs)
        head->object.flags |= SYMMETRIC_LEFT;
 
        if (!istate->cache_nr)
-               read_index(istate);
+               repo_read_index(revs->repo);
        for (i = 0; i < istate->cache_nr; i++) {
                const struct cache_entry *ce = istate->cache[i];
                if (!ce_stage(ce))
@@ -1603,8 +1735,8 @@ static int handle_dotdot_1(const char *arg, char *dotdot,
        if (!*b_name)
                b_name = "HEAD";
 
-       if (get_oid_with_context(a_name, oc_flags, &a_oid, a_oc) ||
-           get_oid_with_context(b_name, oc_flags, &b_oid, b_oc))
+       if (get_oid_with_context(revs->repo, a_name, oc_flags, &a_oid, a_oc) ||
+           get_oid_with_context(revs->repo, b_name, oc_flags, &b_oid, b_oc))
                return -1;
 
        if (!cant_be_filename) {
@@ -1738,7 +1870,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
        if (revarg_opt & REVARG_COMMITTISH)
                get_sha1_flags |= GET_OID_COMMITTISH;
 
-       if (get_oid_with_context(arg, get_sha1_flags, &oid, &oc))
+       if (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc))
                return revs->ignore_missing ? 0 : -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
@@ -2471,7 +2603,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                struct object_id oid;
                struct object *object;
                struct object_context oc;
-               if (get_oid_with_context(revs->def, 0, &oid, &oc))
+               if (get_oid_with_context(revs->repo, revs->def, 0, &oid, &oc))
                        diagnose_missing_default(revs->def);
                object = get_reference(revs, revs->def, &oid, 0);
                add_pending_object_with_mode(revs, object, revs->def, oc.mode);
@@ -3192,14 +3324,14 @@ int prepare_revision_walk(struct rev_info *revs)
        return 0;
 }
 
-static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
+static enum rewrite_result rewrite_one_1(struct rev_info *revs,
+                                        struct commit **pp,
+                                        struct prio_queue *queue)
 {
-       struct commit_list *cache = NULL;
-
        for (;;) {
                struct commit *p = *pp;
                if (!revs->limited)
-                       if (process_parents(revs, p, &revs->commits, &cache) < 0)
+                       if (process_parents(revs, p, NULL, queue) < 0)
                                return rewrite_one_error;
                if (p->object.flags & UNINTERESTING)
                        return rewrite_one_ok;
@@ -3213,6 +3345,31 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
        }
 }
 
+static void merge_queue_into_list(struct prio_queue *q, struct commit_list **list)
+{
+       while (q->nr) {
+               struct commit *item = prio_queue_peek(q);
+               struct commit_list *p = *list;
+
+               if (p && p->item->date >= item->date)
+                       list = &p->next;
+               else {
+                       p = commit_list_insert(item, list);
+                       list = &p->next; /* skip newly added item */
+                       prio_queue_get(q); /* pop item */
+               }
+       }
+}
+
+static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
+{
+       struct prio_queue queue = { compare_commits_by_commit_date };
+       enum rewrite_result ret = rewrite_one_1(revs, pp, &queue);
+       merge_queue_into_list(&queue, &revs->commits);
+       clear_prio_queue(&queue);
+       return ret;
+}
+
 int rewrite_parents(struct rev_info *revs, struct commit *commit,
        rewrite_parent_fn_t rewrite_parent)
 {