]> git.ipfire.org Git - thirdparty/git.git/commitdiff
revision: use priority queue for non-limited streaming walks
authorKristofer Karlsson <krka@spotify.com>
Wed, 27 May 2026 15:50:02 +0000 (15:50 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 May 2026 21:08:20 +0000 (06:08 +0900)
The streaming (non-limited) walk in get_revision_1() inserts newly
discovered parent commits into a date-sorted queue via
commit_list_insert_by_date(), which scans the linked list to find the
insertion point -- O(w) per insert, where w is the width of the active
walk frontier.  Replace this with an O(log w) priority queue.

Add a commit_queue field to rev_info alongside the existing commits
linked list.  The two representations are mutually exclusive: setup
and external callers that need list access use the linked list, then
get_revision_1() lazily drains it into the priority queue on first
call.  Add a REV_WALK_NO_WALK enum value to distinguish the no_walk
case (which still uses the commit list) from the streaming case.

The conversion function rev_info_commit_list_to_queue() is public so
callers that know they will iterate can convert early.

Combined with the limit_list() priority queue change already in
master, this eliminates all O(w) sorted linked-list insertion from
the revision walk machinery.

Signed-off-by: Kristofer Karlsson <krka@spotify.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
commit.c
commit.h
revision.c
revision.h

index e3e7352e69682d45e5b9ef609c656674fc03cb13..5112c7b2af31b164469ebf528945ab025bc258d5 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -729,19 +729,6 @@ void commit_list_free(struct commit_list *list)
                pop_commit(&list);
 }
 
-struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list)
-{
-       struct commit_list **pp = list;
-       struct commit_list *p;
-       while ((p = *pp) != NULL) {
-               if (p->item->date < item->date) {
-                       break;
-               }
-               pp = &p->next;
-       }
-       return commit_list_insert(item, pp);
-}
-
 static int commit_list_compare_by_date(const struct commit_list *a,
                                       const struct commit_list *b)
 {
index 58150045afafed2dceaa952cbb9be7736dd800e8..385492fbb1ecc558f5148827a47fb0058350796c 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -191,8 +191,6 @@ int commit_list_contains(struct commit *item,
 struct commit_list **commit_list_append(struct commit *commit,
                                        struct commit_list **next);
 unsigned commit_list_count(const struct commit_list *l);
-struct commit_list *commit_list_insert_by_date(struct commit *item,
-                                   struct commit_list **list);
 void commit_list_sort_by_date(struct commit_list **list);
 
 /* Shallow copy of the input list */
index 9d0fc696d09937986bab559c77b8d738d167e396..4bb3b16e43acb9aec0d03e49eb54820162dd9f9b 100644 (file)
@@ -1116,7 +1116,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 }
 
 static int process_parents(struct rev_info *revs, struct commit *commit,
-                          struct commit_list **list, struct prio_queue *queue)
+                          struct prio_queue *queue)
 {
        struct commit_list *parent = commit->parents;
        unsigned pass_flags;
@@ -1158,8 +1158,6 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                        if (p->object.flags & SEEN)
                                continue;
                        p->object.flags |= (SEEN | NOT_USER_GIVEN);
-                       if (list)
-                               commit_list_insert_by_date(p, list);
                        if (queue)
                                prio_queue_put(queue, p);
                        if (revs->exclude_first_parent_only)
@@ -1207,8 +1205,6 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
                p->object.flags |= pass_flags | CHILD_VISITED;
                if (!(p->object.flags & SEEN)) {
                        p->object.flags |= (SEEN | NOT_USER_GIVEN);
-                       if (list)
-                               commit_list_insert_by_date(p, list);
                        if (queue)
                                prio_queue_put(queue, p);
                }
@@ -1470,7 +1466,7 @@ static int limit_list(struct rev_info *revs)
 
                if (revs->max_age != -1 && (commit->date < revs->max_age))
                        obj->flags |= UNINTERESTING;
-               if (process_parents(revs, commit, NULL, &queue) < 0) {
+               if (process_parents(revs, commit, &queue) < 0) {
                        clear_prio_queue(&queue);
                        return -1;
                }
@@ -3257,6 +3253,7 @@ static void free_void_commit_list(void *list)
 void release_revisions(struct rev_info *revs)
 {
        commit_list_free(revs->commits);
+       clear_prio_queue(&revs->commit_queue);
        commit_list_free(revs->ancestry_path_bottoms);
        release_display_notes(&revs->notes_opt);
        object_array_clear(&revs->pending);
@@ -3726,7 +3723,7 @@ static void explore_walk_step(struct rev_info *revs)
        if (revs->max_age != -1 && (c->date < revs->max_age))
                c->object.flags |= UNINTERESTING;
 
-       if (process_parents(revs, c, NULL, NULL) < 0)
+       if (process_parents(revs, c, NULL) < 0)
                return;
 
        if (c->object.flags & UNINTERESTING)
@@ -3902,7 +3899,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list *p;
        struct topo_walk_info *info = revs->topo_walk_info;
-       if (process_parents(revs, commit, NULL, NULL) < 0) {
+       if (process_parents(revs, commit, NULL) < 0) {
                if (!revs->ignore_missing_links)
                        die("Failed to traverse parents of commit %s",
                            oid_to_hex(&commit->object.oid));
@@ -3938,6 +3935,13 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit)
        }
 }
 
+void rev_info_commit_list_to_queue(struct rev_info *revs)
+{
+       while (revs->commits)
+               prio_queue_put(&revs->commit_queue, pop_commit(&revs->commits));
+}
+
+
 int prepare_revision_walk(struct rev_info *revs)
 {
        int i;
@@ -4006,7 +4010,7 @@ static enum rewrite_result rewrite_one_1(struct rev_info *revs,
        for (;;) {
                struct commit *p = *pp;
                if (!revs->limited)
-                       if (process_parents(revs, p, NULL, queue) < 0)
+                       if (process_parents(revs, p, queue) < 0)
                                return rewrite_one_error;
                if (p->object.flags & UNINTERESTING)
                        return rewrite_one_ok;
@@ -4020,27 +4024,18 @@ static enum rewrite_result rewrite_one_1(struct rev_info *revs,
        }
 }
 
-static void merge_queue_into_list(struct prio_queue *q, struct commit_list **list)
+static void merge_queue_into_prio_queue(struct prio_queue *from,
+                                       struct prio_queue *to)
 {
-       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 */
-               }
-       }
+       while (from->nr)
+               prio_queue_put(to, prio_queue_get(from));
 }
 
 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);
+       merge_queue_into_prio_queue(&queue, &revs->commit_queue);
        clear_prio_queue(&queue);
        return ret;
 }
@@ -4331,6 +4326,7 @@ enum rev_walk_mode {
        REV_WALK_REFLOG,
        REV_WALK_TOPO,
        REV_WALK_LIMITED,
+       REV_WALK_NO_WALK,
        REV_WALK_STREAMING,
 };
 
@@ -4342,6 +4338,8 @@ static enum rev_walk_mode get_walk_mode(struct rev_info *revs)
                return REV_WALK_TOPO;
        if (revs->limited)
                return REV_WALK_LIMITED;
+       if (revs->no_walk)
+               return REV_WALK_NO_WALK;
        return REV_WALK_STREAMING;
 }
 
@@ -4349,6 +4347,9 @@ static struct commit *get_revision_1(struct rev_info *revs)
 {
        enum rev_walk_mode mode = get_walk_mode(revs);
 
+       if (mode == REV_WALK_STREAMING && revs->commits)
+               rev_info_commit_list_to_queue(revs);
+
        while (1) {
                struct commit *commit;
 
@@ -4360,9 +4361,12 @@ static struct commit *get_revision_1(struct rev_info *revs)
                        commit = next_topo_commit(revs);
                        break;
                case REV_WALK_LIMITED:
-               case REV_WALK_STREAMING:
+               case REV_WALK_NO_WALK:
                        commit = pop_commit(&revs->commits);
                        break;
+               case REV_WALK_STREAMING:
+                       commit = prio_queue_get(&revs->commit_queue);
+                       break;
                }
 
                if (!commit)
@@ -4390,12 +4394,13 @@ static struct commit *get_revision_1(struct rev_info *revs)
                        break;
                case REV_WALK_STREAMING:
                        if (process_parents(revs, commit,
-                                           &revs->commits, NULL) < 0) {
+                                           &revs->commit_queue) < 0) {
                                if (!revs->ignore_missing_links)
                                        die("Failed to traverse parents of commit %s",
                                            oid_to_hex(&commit->object.oid));
                        }
                        break;
+               case REV_WALK_NO_WALK:
                case REV_WALK_LIMITED:
                        break;
                }
index 584f1338b5e323820783a20372095a235f03e4e6..04982a3d47f28fb6325f36877f38629bf0433491 100644 (file)
@@ -12,6 +12,7 @@
 #include "decorate.h"
 #include "ident.h"
 #include "list-objects-filter-options.h"
+#include "prio-queue.h"
 #include "strvec.h"
 
 /**
@@ -122,8 +123,14 @@ struct oidset;
 struct topo_walk_info;
 
 struct rev_info {
-       /* Starting list */
+       /*
+        * Work queue of commits, stored as either a linked list or a
+        * priority queue, but never both at the same time.
+        * rev_info_commit_list_to_queue() converts list to queue.
+        */
        struct commit_list *commits;
+       struct prio_queue commit_queue;
+
        struct object_array pending;
        struct repository *repo;
 
@@ -400,6 +407,7 @@ struct rev_info {
  * uninitialized.
  */
 #define REV_INFO_INIT { \
+       .commit_queue = { .compare = compare_commits_by_commit_date }, \
        .abbrev = DEFAULT_ABBREV, \
        .simplify_history = 1, \
        .pruning.flags.recursive = 1, \
@@ -478,6 +486,8 @@ void reset_revision_walk(void);
  */
 int prepare_revision_walk(struct rev_info *revs);
 
+/* Drain the commits linked list into the priority queue. */
+void rev_info_commit_list_to_queue(struct rev_info *revs);
 /**
  * Takes a pointer to a `rev_info` structure and iterates over it, returning a
  * `struct commit *` each time you call it. The end of the revision list is