Commit fb23d05f0bc3084cdb5a9737b1c678817c5bc9e8

Carlos Martín Nieto 2013-08-17T07:58:55

revwalk: make mark_unintersting use a loop Using a recursive function can blow the stack when dealing with long histories. Use a loop instead to limit the call chain depth. This fixes #1223.

diff --git a/src/array.h b/src/array.h
index c25a1b2..b82079b 100644
--- a/src/array.h
+++ b/src/array.h
@@ -63,6 +63,8 @@ GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
 
 #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL)
 
+#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : NULL)
+
 #define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL)
 
 #define git_array_size(a) (a).size
diff --git a/src/revwalk.c b/src/revwalk.c
index 528d02b..1ff41bf 100644
--- a/src/revwalk.c
+++ b/src/revwalk.c
@@ -41,28 +41,50 @@ git_commit_list_node *git_revwalk__commit_lookup(
 	return commit;
 }
 
-static void mark_uninteresting(git_commit_list_node *commit)
+static int mark_uninteresting(git_commit_list_node *commit)
 {
 	unsigned short i;
+	git_array_t(git_commit_list_node *) pending = GIT_ARRAY_INIT;
+	git_commit_list_node **tmp;
+
 	assert(commit);
 
-	commit->uninteresting = 1;
+	git_array_alloc(pending);
+	GITERR_CHECK_ARRAY(pending);
 
-	/* This means we've reached a merge base, so there's no need to walk any more */
-	if ((commit->flags & (RESULT | STALE)) == RESULT)
-		return;
+	do {
+		commit->uninteresting = 1;
+
+		/* This means we've reached a merge base, so there's no need to walk any more */
+		if ((commit->flags & (RESULT | STALE)) == RESULT) {
+			tmp = git_array_pop(pending);
+			commit = tmp ? *tmp : NULL;
+			continue;
+		}
+
+		for (i = 0; i < commit->out_degree; ++i)
+			if (!commit->parents[i]->uninteresting) {
+				git_commit_list_node **node = git_array_alloc(pending);
+				GITERR_CHECK_ALLOC(node);
+				*node = commit->parents[i];
+			}
+
+		tmp = git_array_pop(pending);
+		commit = tmp ? *tmp : NULL;
+
+	} while (git_array_size(pending) > 0);
 
-	for (i = 0; i < commit->out_degree; ++i)
-		if (!commit->parents[i]->uninteresting)
-			mark_uninteresting(commit->parents[i]);
+	git_array_clear(pending);
+
+	return 0;
 }
 
 static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide)
 {
 	int error;
 
-	if (hide)
-		mark_uninteresting(commit);
+	if (hide && mark_uninteresting(commit) < 0)
+		return -1;
 
 	if (commit->seen)
 		return 0;