Introduce reduce_heads()
authorJunio C Hamano <gitster@pobox.com>
Fri, 27 Jun 2008 16:22:03 +0000 (18:22 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 1 Jul 2008 05:45:51 +0000 (22:45 -0700)
The new function reduce_heads() is given a list of commits, and removes
ones that can be reached from other commits in the list.  It is useful for
reducing the commits randomly thrown at the git-merge command and remove
redundant commits that the user shouldn't have given to it.

The implementation uses the get_merge_bases_many() introduced in the
previous commit.  If the merge base between one commit taken from the list
and the remaining commits is the commit itself, that means the commit is
reachable from some of the other commits.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
commit.c
commit.h

index cafed26928cf6ef35fffdd41a8686c32e4fe5ca0..d20b14ee3efa7167070767aae0d130a978fac401 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -725,3 +725,48 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num)
        free_commit_list(bases);
        return ret;
 }
+
+struct commit_list *reduce_heads(struct commit_list *heads)
+{
+       struct commit_list *p;
+       struct commit_list *result = NULL, **tail = &result;
+       struct commit **other;
+       size_t num_head, num_other;
+
+       if (!heads)
+               return NULL;
+
+       /* Avoid unnecessary reallocations */
+       for (p = heads, num_head = 0; p; p = p->next)
+               num_head++;
+       other = xcalloc(sizeof(*other), num_head);
+
+       /* For each commit, see if it can be reached by others */
+       for (p = heads; p; p = p->next) {
+               struct commit_list *q, *base;
+
+               num_other = 0;
+               for (q = heads; q; q = q->next) {
+                       if (p == q)
+                               continue;
+                       other[num_other++] = q->item;
+               }
+               if (num_other) {
+                       base = get_merge_bases_many(p->item, num_other, other, 1);
+               } else
+                       base = NULL;
+               /*
+                * If p->item does not have anything common with other
+                * commits, there won't be any merge base.  If it is
+                * reachable from some of the others, p->item will be
+                * the merge base.  If its history is connected with
+                * others, but p->item is not reachable by others, we
+                * will get something other than p->item back.
+                */
+               if (!base || (base->item != p->item))
+                       tail = &(commit_list_insert(p->item, tail)->next);
+               free_commit_list(base);
+       }
+       free(other);
+       return result;
+}
index dcec7fb9a2264d068037aa57894ba0a3ae43231a..2acfc79d3483b4c83c1c181a8ac1505393e96ee5 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -140,4 +140,6 @@ static inline int single_parent(struct commit *commit)
        return commit->parents && !commit->parents->next;
 }
 
+struct commit_list *reduce_heads(struct commit_list *heads);
+
 #endif /* COMMIT_H */