libgit2/tests-clar/diff/diffiter.c
Russell Belfer 3b5f795446 Create git_diff_line and extend git_diff_hunk
Instead of having functions with so very many parameters to pass
hunk and line data, this takes the existing git_diff_hunk struct
and extends it with more hunk data, plus adds a git_diff_line.
Those structs are used to pass back hunk and line data instead of
the old APIs that took tons of parameters.

Some work that was previously only being done for git_diff_patch
creation (scanning the diff content for exact line counts) is now
done for all callbacks, but the performance difference should not
be noticable.
2013-10-21 13:42:42 -07:00

454 lines
11 KiB
C

#include "clar_libgit2.h"
#include "diff_helpers.h"
void test_diff_diffiter__initialize(void)
{
}
void test_diff_diffiter__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_diff_diffiter__create(void)
{
git_repository *repo = cl_git_sandbox_init("attr");
git_diff *diff;
size_t d, num_d;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
const git_diff_delta *delta = git_diff_get_delta(diff, d);
cl_assert(delta != NULL);
}
cl_assert(!git_diff_get_delta(diff, num_d));
git_diff_free(diff);
}
void test_diff_diffiter__iterate_files_1(void)
{
git_repository *repo = cl_git_sandbox_init("attr");
git_diff *diff;
size_t d, num_d;
diff_expects exp = { 0 };
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
const git_diff_delta *delta = git_diff_get_delta(diff, d);
cl_assert(delta != NULL);
diff_file_cb(delta, (float)d / (float)num_d, &exp);
}
cl_assert_equal_sz(6, exp.files);
git_diff_free(diff);
}
void test_diff_diffiter__iterate_files_2(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff *diff;
size_t d, num_d;
int count = 0;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(8, (int)num_d);
for (d = 0; d < num_d; ++d) {
const git_diff_delta *delta = git_diff_get_delta(diff, d);
cl_assert(delta != NULL);
count++;
}
cl_assert_equal_i(8, count);
git_diff_free(diff);
}
void test_diff_diffiter__iterate_files_and_hunks(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff = NULL;
size_t d, num_d;
int file_count = 0, hunk_count = 0;
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
git_patch *patch;
size_t h, num_h;
cl_git_pass(git_patch_from_diff(&patch, diff, d));
cl_assert(patch);
file_count++;
num_h = git_patch_num_hunks(patch);
for (h = 0; h < num_h; h++) {
const git_diff_hunk *hunk;
cl_git_pass(git_patch_get_hunk(&hunk, NULL, patch, h));
cl_assert(hunk);
hunk_count++;
}
git_patch_free(patch);
}
cl_assert_equal_i(13, file_count);
cl_assert_equal_i(8, hunk_count);
git_diff_free(diff);
}
void test_diff_diffiter__max_size_threshold(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff = NULL;
int file_count = 0, binary_count = 0, hunk_count = 0;
size_t d, num_d;
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
git_patch *patch;
const git_diff_delta *delta;
cl_git_pass(git_patch_from_diff(&patch, diff, d));
cl_assert(patch);
delta = git_patch_get_delta(patch);
cl_assert(delta);
file_count++;
hunk_count += (int)git_patch_num_hunks(patch);
assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0);
binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
git_patch_free(patch);
}
cl_assert_equal_i(13, file_count);
cl_assert_equal_i(0, binary_count);
cl_assert_equal_i(8, hunk_count);
git_diff_free(diff);
/* try again with low file size threshold */
file_count = binary_count = hunk_count = 0;
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
opts.max_size = 50; /* treat anything over 50 bytes as binary! */
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
git_patch *patch;
const git_diff_delta *delta;
cl_git_pass(git_patch_from_diff(&patch, diff, d));
delta = git_patch_get_delta(patch);
file_count++;
hunk_count += (int)git_patch_num_hunks(patch);
assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0);
binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
git_patch_free(patch);
}
cl_assert_equal_i(13, file_count);
/* Three files are over the 50 byte threshold:
* - staged_changes_file_deleted
* - staged_changes_modified_file
* - staged_new_file_modified_file
*/
cl_assert_equal_i(3, binary_count);
cl_assert_equal_i(5, hunk_count);
git_diff_free(diff);
}
void test_diff_diffiter__iterate_all(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff = NULL;
diff_expects exp = {0};
size_t d, num_d;
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
git_patch *patch;
size_t h, num_h;
cl_git_pass(git_patch_from_diff(&patch, diff, d));
cl_assert(patch);
exp.files++;
num_h = git_patch_num_hunks(patch);
for (h = 0; h < num_h; h++) {
const git_diff_hunk *range;
size_t l, num_l;
cl_git_pass(git_patch_get_hunk(&range, &num_l, patch, h));
cl_assert(range);
exp.hunks++;
for (l = 0; l < num_l; ++l) {
const git_diff_line *line;
cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l));
cl_assert(line && line->content);
exp.lines++;
}
}
git_patch_free(patch);
}
cl_assert_equal_i(13, exp.files);
cl_assert_equal_i(8, exp.hunks);
cl_assert_equal_i(14, exp.lines);
git_diff_free(diff);
}
static void iterate_over_patch(git_patch *patch, diff_expects *exp)
{
size_t h, num_h = git_patch_num_hunks(patch), num_l;
exp->files++;
exp->hunks += (int)num_h;
/* let's iterate in reverse, just because we can! */
for (h = 1, num_l = 0; h <= num_h; ++h)
num_l += git_patch_num_lines_in_hunk(patch, num_h - h);
exp->lines += (int)num_l;
}
#define PATCH_CACHE 5
void test_diff_diffiter__iterate_randomly_while_saving_state(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff = NULL;
diff_expects exp = {0};
git_patch *patches[PATCH_CACHE];
size_t p, d, num_d;
memset(patches, 0, sizeof(patches));
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
/* To make sure that references counts work for diff and patch objects,
* this generates patches and randomly caches them. Only when the patch
* is removed from the cache are hunks and lines counted. At the end,
* there are still patches in the cache, so free the diff and try to
* process remaining patches after the diff is freed.
*/
srand(121212);
p = rand() % PATCH_CACHE;
for (d = 0; d < num_d; ++d) {
/* take old patch */
git_patch *patch = patches[p];
patches[p] = NULL;
/* cache new patch */
cl_git_pass(git_patch_from_diff(&patches[p], diff, d));
cl_assert(patches[p] != NULL);
/* process old patch if non-NULL */
if (patch != NULL) {
iterate_over_patch(patch, &exp);
git_patch_free(patch);
}
p = rand() % PATCH_CACHE;
}
/* free diff list now - refcounts should keep things safe */
git_diff_free(diff);
/* process remaining unprocessed patches */
for (p = 0; p < PATCH_CACHE; p++) {
git_patch *patch = patches[p];
if (patch != NULL) {
iterate_over_patch(patch, &exp);
git_patch_free(patch);
}
}
/* hopefully it all still added up right */
cl_assert_equal_i(13, exp.files);
cl_assert_equal_i(8, exp.hunks);
cl_assert_equal_i(14, exp.lines);
}
/* This output is taken directly from `git diff` on the status test data */
static const char *expected_patch_text[8] = {
/* 0 */
"diff --git a/file_deleted b/file_deleted\n"
"deleted file mode 100644\n"
"index 5452d32..0000000\n"
"--- a/file_deleted\n"
"+++ /dev/null\n"
"@@ -1 +0,0 @@\n"
"-file_deleted\n",
/* 1 */
"diff --git a/modified_file b/modified_file\n"
"index 452e424..0a53963 100644\n"
"--- a/modified_file\n"
"+++ b/modified_file\n"
"@@ -1 +1,2 @@\n"
" modified_file\n"
"+modified_file\n",
/* 2 */
"diff --git a/staged_changes_file_deleted b/staged_changes_file_deleted\n"
"deleted file mode 100644\n"
"index a6be623..0000000\n"
"--- a/staged_changes_file_deleted\n"
"+++ /dev/null\n"
"@@ -1,2 +0,0 @@\n"
"-staged_changes_file_deleted\n"
"-staged_changes_file_deleted\n",
/* 3 */
"diff --git a/staged_changes_modified_file b/staged_changes_modified_file\n"
"index 906ee77..011c344 100644\n"
"--- a/staged_changes_modified_file\n"
"+++ b/staged_changes_modified_file\n"
"@@ -1,2 +1,3 @@\n"
" staged_changes_modified_file\n"
" staged_changes_modified_file\n"
"+staged_changes_modified_file\n",
/* 4 */
"diff --git a/staged_new_file_deleted_file b/staged_new_file_deleted_file\n"
"deleted file mode 100644\n"
"index 90b8c29..0000000\n"
"--- a/staged_new_file_deleted_file\n"
"+++ /dev/null\n"
"@@ -1 +0,0 @@\n"
"-staged_new_file_deleted_file\n",
/* 5 */
"diff --git a/staged_new_file_modified_file b/staged_new_file_modified_file\n"
"index ed06290..8b090c0 100644\n"
"--- a/staged_new_file_modified_file\n"
"+++ b/staged_new_file_modified_file\n"
"@@ -1 +1,2 @@\n"
" staged_new_file_modified_file\n"
"+staged_new_file_modified_file\n",
/* 6 */
"diff --git a/subdir/deleted_file b/subdir/deleted_file\n"
"deleted file mode 100644\n"
"index 1888c80..0000000\n"
"--- a/subdir/deleted_file\n"
"+++ /dev/null\n"
"@@ -1 +0,0 @@\n"
"-subdir/deleted_file\n",
/* 7 */
"diff --git a/subdir/modified_file b/subdir/modified_file\n"
"index a619198..57274b7 100644\n"
"--- a/subdir/modified_file\n"
"+++ b/subdir/modified_file\n"
"@@ -1 +1,2 @@\n"
" subdir/modified_file\n"
"+subdir/modified_file\n"
};
void test_diff_diffiter__iterate_and_generate_patch_text(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff *diff;
size_t d, num_d;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(8, (int)num_d);
for (d = 0; d < num_d; ++d) {
git_patch *patch;
char *text;
cl_git_pass(git_patch_from_diff(&patch, diff, d));
cl_assert(patch != NULL);
cl_git_pass(git_patch_to_str(&text, patch));
cl_assert_equal_s(expected_patch_text[d], text);
git__free(text);
git_patch_free(patch);
}
git_diff_free(diff);
}
void test_diff_diffiter__checks_options_version(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff = NULL;
const git_error *err;
opts.version = 0;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
err = giterr_last();
cl_assert_equal_i(GITERR_INVALID, err->klass);
giterr_clear();
opts.version = 1024;
cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
err = giterr_last();
cl_assert_equal_i(GITERR_INVALID, err->klass);
}