Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit 105500f

Browse files
committed
Merge branch 'tag-contains'
This topic branch addresses out-of-memory errors in particular on Windows, where the default stack space is not very large. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 0fce85d + 843ef23 commit 105500f

File tree

2 files changed

+88
-14
lines changed

2 files changed

+88
-14
lines changed

builtin/tag.c

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,38 +73,91 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
7373
return 0;
7474
}
7575

76-
static int contains_recurse(struct commit *candidate,
76+
/*
77+
* Test whether the candidate or one of its parents is contained in the list.
78+
* Do not recurse to find out, though, but return -1 if inconclusive.
79+
*/
80+
static int contains_test(struct commit *candidate,
7781
const struct commit_list *want)
7882
{
79-
struct commit_list *p;
80-
8183
/* was it previously marked as containing a want commit? */
8284
if (candidate->object.flags & TMP_MARK)
8385
return 1;
8486
/* or marked as not possibly containing a want commit? */
8587
if (candidate->object.flags & UNINTERESTING)
8688
return 0;
8789
/* or are we it? */
88-
if (in_commit_list(want, candidate))
90+
if (in_commit_list(want, candidate)) {
91+
candidate->object.flags |= TMP_MARK;
8992
return 1;
93+
}
9094

9195
if (parse_commit(candidate) < 0)
9296
return 0;
9397

94-
/* Otherwise recurse and mark ourselves for future traversals. */
95-
for (p = candidate->parents; p; p = p->next) {
96-
if (contains_recurse(p->item, want)) {
97-
candidate->object.flags |= TMP_MARK;
98-
return 1;
99-
}
100-
}
101-
candidate->object.flags |= UNINTERESTING;
102-
return 0;
98+
return -1;
99+
}
100+
101+
/*
102+
* Mimicking the real stack, this stack lives on the heap, avoiding stack
103+
* overflows.
104+
*
105+
* At each recursion step, the stack items points to the commits whose
106+
* ancestors are to be inspected.
107+
*/
108+
struct stack {
109+
int nr, alloc;
110+
struct stack_entry {
111+
struct commit *commit;
112+
struct commit_list *parents;
113+
} *stack;
114+
};
115+
116+
static void push_to_stack(struct commit *candidate, struct stack *stack)
117+
{
118+
int index = stack->nr++;
119+
ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
120+
stack->stack[index].commit = candidate;
121+
stack->stack[index].parents = candidate->parents;
103122
}
104123

105124
static int contains(struct commit *candidate, const struct commit_list *want)
106125
{
107-
return contains_recurse(candidate, want);
126+
struct stack stack = { 0, 0, NULL };
127+
int result = contains_test(candidate, want);
128+
129+
if (result >= 0)
130+
return result;
131+
132+
push_to_stack(candidate, &stack);
133+
while (stack.nr) {
134+
struct stack_entry *entry = &stack.stack[stack.nr - 1];
135+
struct commit *commit = entry->commit;
136+
struct commit_list *parents = entry->parents;
137+
138+
if (!parents) {
139+
commit->object.flags = UNINTERESTING;
140+
stack.nr--;
141+
}
142+
/*
143+
* If we just popped the stack, parents->item has been marked,
144+
* therefore contains_test will return a meaningful 0 or 1.
145+
*/
146+
else switch (contains_test(parents->item, want)) {
147+
case 1:
148+
commit->object.flags |= TMP_MARK;
149+
stack.nr--;
150+
break;
151+
case 0:
152+
entry->parents = parents->next;
153+
break;
154+
default:
155+
push_to_stack(parents->item, &stack);
156+
break;
157+
}
158+
}
159+
free(stack.stack);
160+
return contains_test(candidate, want);
108161
}
109162

110163
static void show_tag_lines(const unsigned char *sha1, int lines)

t/t7004-tag.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,4 +1380,25 @@ test_expect_success 'multiple --points-at are OR-ed together' '
13801380
test_cmp expect actual
13811381
'
13821382
1383+
>expect
1384+
# ulimit is a bash builtin; we can rely on that in MinGW, but nowhere else
1385+
test_expect_success MINGW '--contains works in a deep repo' '
1386+
ulimit -s 64
1387+
i=1 &&
1388+
while test $i -lt 1000
1389+
do
1390+
echo "commit refs/heads/master
1391+
committer A U Thor <[email protected]> $((1000000000 + $i * 100)) +0200
1392+
data <<EOF
1393+
commit #$i
1394+
EOF"
1395+
test $i = 1 && echo "from refs/heads/master^0"
1396+
i=$(($i + 1))
1397+
done | git fast-import &&
1398+
git checkout master &&
1399+
git tag far-far-away HEAD^ &&
1400+
git tag --contains HEAD >actual &&
1401+
test_cmp expect actual
1402+
'
1403+
13831404
test_done

0 commit comments

Comments
 (0)