Merge pull request #1131 from libgit2/correct-ahead-behind Fix an issue with ahead-behind for lopsided traversal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
diff --git a/src/graph.c b/src/graph.c
index fd789d6..e61fc84 100644
--- a/src/graph.c
+++ b/src/graph.c
@@ -10,6 +10,101 @@
#include "merge.h"
#include "git2/graph.h"
+static int interesting(git_pqueue *list, git_commit_list *roots)
+{
+ unsigned int i;
+ /* element 0 isn't used - we need to start at 1 */
+ for (i = 1; i < list->size; i++) {
+ git_commit_list_node *commit = list->d[i];
+ if ((commit->flags & STALE) == 0)
+ return 1;
+ }
+
+ while(roots) {
+ if ((roots->item->flags & STALE) == 0)
+ return 1;
+ roots = roots->next;
+ }
+
+ return 0;
+}
+
+static int mark_parents(git_revwalk *walk, git_commit_list_node *one,
+ git_commit_list_node *two)
+{
+ unsigned int i;
+ git_commit_list *roots = NULL;
+ git_pqueue list;
+
+ /* if the commit is repeated, we have a our merge base already */
+ if (one == two) {
+ one->flags |= PARENT1 | PARENT2 | RESULT;
+ return 0;
+ }
+
+ if (git_pqueue_init(&list, 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if (git_commit_list_parse(walk, one) < 0)
+ goto on_error;
+ one->flags |= PARENT1;
+ if (git_pqueue_insert(&list, one) < 0)
+ goto on_error;
+
+ if (git_commit_list_parse(walk, two) < 0)
+ goto on_error;
+ two->flags |= PARENT2;
+ if (git_pqueue_insert(&list, two) < 0)
+ goto on_error;
+
+ /* as long as there are non-STALE commits */
+ while (interesting(&list, roots)) {
+ git_commit_list_node *commit;
+ int flags;
+
+ commit = git_pqueue_pop(&list);
+ if (commit == NULL)
+ break;
+
+ flags = commit->flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->flags & RESULT))
+ commit->flags |= RESULT;
+ /* we mark the parents of a merge stale */
+ flags |= STALE;
+ }
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((p->flags & flags) == flags)
+ continue;
+
+ if (git_commit_list_parse(walk, p) < 0)
+ goto on_error;
+
+ p->flags |= flags;
+ if (git_pqueue_insert(&list, p) < 0)
+ goto on_error;
+ }
+
+ /* Keep track of root commits, to make sure the path gets marked */
+ if (commit->out_degree == 0) {
+ if (git_commit_list_insert(commit, &roots) == NULL)
+ goto on_error;
+ }
+ }
+
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return 0;
+
+on_error:
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return -1;
+}
+
+
static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two,
size_t *ahead, size_t *behind)
{
@@ -55,10 +150,7 @@ int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,
const git_oid *one, const git_oid *two)
{
git_revwalk *walk;
- git_vector list;
- struct git_commit_list *result = NULL;
git_commit_list_node *commit1, *commit2;
- void *contents[1];
if (git_revwalk_new(&walk, repo) < 0)
return -1;
@@ -67,27 +159,15 @@ int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,
if (commit2 == NULL)
goto on_error;
- /* This is just one value, so we can do it on the stack */
- memset(&list, 0x0, sizeof(git_vector));
- contents[0] = commit2;
- list.length = 1;
- list.contents = contents;
-
commit1 = git_revwalk__commit_lookup(walk, one);
if (commit1 == NULL)
goto on_error;
- if (git_merge__bases_many(&result, walk, commit1, &list) < 0)
+ if (mark_parents(walk, commit1, commit2) < 0)
goto on_error;
if (ahead_behind(commit1, commit2, ahead, behind) < 0)
goto on_error;
- if (!result) {
- git_revwalk_free(walk);
- return GIT_ENOTFOUND;
- }
-
- git_commit_list_free(&result);
git_revwalk_free(walk);
return 0;
diff --git a/tests-clar/revwalk/mergebase.c b/tests-clar/revwalk/mergebase.c
index 4faf615..293a585 100644
--- a/tests-clar/revwalk/mergebase.c
+++ b/tests-clar/revwalk/mergebase.c
@@ -118,10 +118,9 @@ void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void)
cl_assert_equal_i(GIT_ENOTFOUND, error);
- cl_git_fail(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two));
- cl_git_fail(error);
-
- cl_assert_equal_i(GIT_ENOTFOUND, error);
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two));
+ cl_assert_equal_i(2, ahead);
+ cl_assert_equal_i(4, behind);
}
void test_revwalk_mergebase__no_off_by_one_missing(void)