Commit f1e2735c by Russell Belfer

Add helper for diff line stats

This adds a `git_diff_patch_line_stats()` API that gets the total
number of adds, deletes, and context lines in a patch.  This will
make it a little easier to emulate `git diff --stat` and the like.

Right now, this relies on generating the `git_diff_patch` object,
which is a pretty heavyweight way to get stat information.  At
some future point, it would probably be nice to be able to get
this information without allocating the entire `git_diff_patch`,
but that's a much larger project.
parent d2041216
......@@ -704,6 +704,28 @@ GIT_EXTERN(size_t) git_diff_patch_num_hunks(
git_diff_patch *patch);
/**
* Get line counts of each type in a patch.
*
* This helps imitate a diff --numstat type of output. For that purpose,
* you only need the `total_additions` and `total_deletions` values, but we
* include the `total_context` line count in case you want the total number
* of lines of diff output that will be generated.
*
* All outputs are optional. Pass NULL if you don't need a particular count.
*
* @param total_context Count of context lines in output, can be NULL.
* @param total_additions Count of addition lines in output, can be NULL.
* @param total_deletions Count of deletion lines in output, can be NULL.
* @param patch The git_diff_patch object
* @return Number of lines in hunk or -1 if invalid hunk index
*/
GIT_EXTERN(int) git_diff_patch_line_stats(
size_t *total_context,
size_t *total_additions,
size_t *total_deletions,
const git_diff_patch *patch);
/**
* Get the information about a hunk in a patch
*
* Given a patch and a hunk index into the patch, this returns detailed
......
......@@ -134,6 +134,13 @@ GIT_INLINE(ssize_t) git_buf_rfind(git_buf *buf, char ch)
return idx;
}
GIT_INLINE(ssize_t) git_buf_find(git_buf *buf, char ch)
{
size_t idx = 0;
while (idx < buf->size && buf->ptr[idx] != ch) idx++;
return (idx == buf->size) ? -1 : (ssize_t)idx;
}
/* Remove whitespace from the end of the buffer */
void git_buf_rtrim(git_buf *buf);
......
......@@ -1501,6 +1501,39 @@ size_t git_diff_patch_num_hunks(git_diff_patch *patch)
return patch->hunks_size;
}
int git_diff_patch_line_stats(
size_t *total_ctxt,
size_t *total_adds,
size_t *total_dels,
const git_diff_patch *patch)
{
size_t totals[3], idx;
memset(totals, 0, sizeof(totals));
for (idx = 0; idx < patch->lines_size; ++idx) {
switch (patch->lines[idx].origin) {
case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
case GIT_DIFF_LINE_DELETION: totals[2]++; break;
default:
/* diff --stat and --numstat don't count EOFNL marks because
* they will always be paired with a ADDITION or DELETION line.
*/
break;
}
}
if (total_ctxt)
*total_ctxt = totals[0];
if (total_adds)
*total_adds = totals[1];
if (total_dels)
*total_dels = totals[2];
return 0;
}
int git_diff_patch_get_hunk(
const git_diff_range **range,
const char **header,
......@@ -1706,4 +1739,3 @@ int git_diff__paired_foreach(
return 0;
}
......@@ -235,3 +235,68 @@ void test_diff_patch__hunks_have_correct_line_numbers(void)
git_diff_list_free(diff);
git_tree_free(head);
}
static void check_single_patch_stats(
git_repository *repo, size_t hunks, size_t adds, size_t dels)
{
git_diff_list *diff;
git_diff_patch *patch;
const git_diff_delta *delta;
size_t actual_adds, actual_dels;
cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
cl_git_pass(git_diff_get_patch(&patch, &delta, diff, 0));
cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
cl_assert_equal_i(hunks, (int)git_diff_patch_num_hunks(patch));
cl_git_pass(
git_diff_patch_line_stats(NULL, &actual_adds, &actual_dels, patch));
cl_assert_equal_i(adds, actual_adds);
cl_assert_equal_i(dels, actual_dels);
git_diff_patch_free(patch);
git_diff_list_free(diff);
}
void test_diff_patch__line_counts_with_eofnl(void)
{
git_buf content = GIT_BUF_INIT;
const char *end;
git_index *index;
g_repo = cl_git_sandbox_init("renames");
cl_git_pass(git_futils_readbuffer(&content, "renames/songofseven.txt"));
/* remove first line */
end = git_buf_cstr(&content) + git_buf_find(&content, '\n') + 1;
git_buf_consume(&content, end);
cl_git_rewritefile("renames/songofseven.txt", content.ptr);
check_single_patch_stats(g_repo, 1, 0, 1);
/* remove trailing whitespace */
git_buf_rtrim(&content);
cl_git_rewritefile("renames/songofseven.txt", content.ptr);
check_single_patch_stats(g_repo, 2, 1, 2);
/* add trailing whitespace */
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_index_add_bypath(index, "songofseven.txt"));
cl_git_pass(git_index_write(index));
git_index_free(index);
cl_git_pass(git_buf_putc(&content, '\n'));
cl_git_rewritefile("renames/songofseven.txt", content.ptr);
check_single_patch_stats(g_repo, 1, 1, 1);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment