summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <cmn@dwim.me>2014-10-08 15:52:11 +0200
committerCarlos Martín Nieto <cmn@dwim.me>2014-10-08 15:52:11 +0200
commite7970576f1bcef1561813dbef339a31efabdc9b1 (patch)
tree2622a5d87f04dda3375d1772ad1068fa750c8bdb
parentad66bf88df087bee34bb0dd47a9eacfdbef89c57 (diff)
downloadlibgit2-e7970576f1bcef1561813dbef339a31efabdc9b1.tar.gz
revwalk: mark uninteresting only up to the common ancestors
This introduces a phase at the start of preparing a walk which pre-marks uninteresting commits, but only up to the common ancestors. We do this in a similar way to git, by walking down the history and marking (which is what we used to do), but we keep a time-sorted priority queue of commits and stop marking as soon as there are only uninteresting commits in this queue. This is a similar rule to the one used to find the merge-base. As we keep inserting commits regardless of the uninteresting bit, if there are only uninteresting commits in the queue, it means we've run out of interesting commits in our walk, so we can stop. The old mark_unintesting() logic is still in place, but that stops walking if it finds an already-uninteresting commit, so it will stop on the ones we've pre-marked; but keeping it allows us to also hide those that are hidden via the callback.
-rw-r--r--src/revwalk.c74
1 files changed, 73 insertions, 1 deletions
diff --git a/src/revwalk.c b/src/revwalk.c
index 8a5a8464e..a1d761f1b 100644
--- a/src/revwalk.c
+++ b/src/revwalk.c
@@ -369,19 +369,91 @@ static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *
}
+static int interesting(git_pqueue *list)
+{
+ size_t i;
+
+ for (i = 0; i < git_pqueue_size(list); i++) {
+ git_commit_list_node *commit = git_pqueue_get(list, i);
+ if (!commit->uninteresting)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int contains(git_pqueue *list, git_commit_list_node *node)
+{
+ size_t i;
+
+ for (i = 0; i < git_pqueue_size(list); i++) {
+ git_commit_list_node *commit = git_pqueue_get(list, i);
+ if (commit == node)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int premark_uninteresting(git_revwalk *walk)
+{
+ int error = 0;
+ unsigned short i;
+ git_pqueue q;
+ git_commit_list *list;
+ git_commit_list_node *commit, *parent;
+
+ if ((error = git_pqueue_init(&q, 0, 8, git_commit_list_time_cmp)) < 0)
+ return error;
+
+ for (list = walk->user_input; list; list = list->next) {
+ if ((error = git_commit_list_parse(walk, list->item)) < 0)
+ goto cleanup;
+
+ if ((error = git_pqueue_insert(&q, list->item)) < 0)
+ goto cleanup;
+ }
+
+ while (interesting(&q)) {
+ commit = git_pqueue_pop(&q);
+
+ for (i = 0; i < commit->out_degree; i++) {
+ parent = commit->parents[i];
+
+ if ((error = git_commit_list_parse(walk, parent)) < 0)
+ goto cleanup;
+
+ if (commit->uninteresting)
+ parent->uninteresting = 1;
+
+ if (contains(&q, parent))
+ continue;
+
+ if ((error = git_pqueue_insert(&q, parent)) < 0)
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ git_pqueue_free(&q);
+ return error;
+}
+
static int prepare_walk(git_revwalk *walk)
{
int error, interesting = 0;
git_commit_list *list;
git_commit_list_node *next;
+ if ((error = premark_uninteresting(walk)) < 0)
+ return error;
+
for (list = walk->user_input; list; list = list->next) {
interesting += !list->item->uninteresting;
if (process_commit(walk, list->item, list->item->uninteresting) < 0)
return -1;
}
-
/*
* If there were no pushes, we know that the walk is already over.
*/