Skip to content

Commit f333dfc

Browse files
committed
repo: catch exceptions when iterating through status
When we call the block, an exception or even a call to `break` would cause us to unwind the stack and skip any resource freeing that we may have. The solution is to call `rb_protect` which will let us catch the exception and raise whenever we need to. Doing this as part of a `git_status_foreach` is unnecessarily awkward so move to iterating over the status list ourselves and free the entry list as soon as we notice there was an exception. For example, this program will consume memory indefinitely: require 'rugged' repo = Rugged::Repository.new ARGV[0] loop do repo.status do |file, status_data| break end end Thanks to Aaron Patterson for discovering this and the original hacky fix.
1 parent 2e2e1ea commit f333dfc

File tree

2 files changed

+46
-16
lines changed

2 files changed

+46
-16
lines changed

ext/rugged/rugged_repo.c

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,15 +1502,6 @@ static VALUE flags_to_rb(unsigned int flags)
15021502
return rb_flags;
15031503
}
15041504

1505-
static int rugged__status_cb(const char *path, unsigned int flags, void *payload)
1506-
{
1507-
rb_funcall((VALUE)payload, rb_intern("call"), 2,
1508-
rb_str_new_utf8(path), flags_to_rb(flags)
1509-
);
1510-
1511-
return GIT_OK;
1512-
}
1513-
15141505
static VALUE rb_git_repo_file_status(VALUE self, VALUE rb_path)
15151506
{
15161507
unsigned int flags;
@@ -1527,8 +1518,10 @@ static VALUE rb_git_repo_file_status(VALUE self, VALUE rb_path)
15271518

15281519
static VALUE rb_git_repo_file_each_status(VALUE self)
15291520
{
1530-
int error;
1521+
int error, exception;
1522+
size_t i, nentries;
15311523
git_repository *repo;
1524+
git_status_list *list;
15321525

15331526
Data_Get_Struct(self, git_repository, repo);
15341527

@@ -1537,13 +1530,30 @@ static VALUE rb_git_repo_file_each_status(VALUE self)
15371530
"A block was expected for iterating through "
15381531
"the repository contents.");
15391532

1540-
error = git_status_foreach(
1541-
repo,
1542-
&rugged__status_cb,
1543-
(void *)rb_block_proc()
1544-
);
1545-
1533+
error = git_status_list_new(&list, repo, NULL);
15461534
rugged_exception_check(error);
1535+
1536+
nentries = git_status_list_entrycount(list);
1537+
for (i = 0; i < nentries; i++) {
1538+
const git_status_entry *entry;
1539+
const char *path;
1540+
VALUE args;
1541+
1542+
entry = git_status_byindex(list, i);
1543+
1544+
path = entry->head_to_index ?
1545+
entry->head_to_index->old_file.path :
1546+
entry->index_to_workdir->old_file.path;
1547+
args = rb_ary_new3(2, rb_str_new_utf8(path), flags_to_rb(entry->status));
1548+
rb_protect(rb_yield, args, &exception);
1549+
if (exception != 0)
1550+
break;
1551+
}
1552+
git_status_list_free(list);
1553+
1554+
if (exception != 0)
1555+
rb_jump_tag(exception);
1556+
15471557
return Qnil;
15481558
}
15491559

test/status_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ def setup
3333
@repo = FixtureRepo.from_libgit2 "status"
3434
end
3535

36+
class TestException < RuntimeError
37+
end
38+
39+
def test_status_block_raises
40+
assert_raises(TestException) do
41+
@repo.status do |file, status|
42+
raise TestException, "wow"
43+
end
44+
end
45+
end
46+
47+
def test_status_block_breaks
48+
yielded = 0
49+
@repo.status do |file, status|
50+
yielded += 1
51+
break
52+
end
53+
assert_equal 1, yielded
54+
end
55+
3656
def test_status_with_callback
3757
actual_statuses = {}
3858
@repo.status do |file, status|

0 commit comments

Comments
 (0)