summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commit.c45
-rw-r--r--commit.h2
2 files changed, 47 insertions, 0 deletions
diff --git a/commit.c b/commit.c
index cafed26928..d20b14ee3e 100644
--- 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;
+}
diff --git a/commit.h b/commit.h
index dcec7fb9a2..2acfc79d34 100644
--- 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 */