Commit 5e96f316 by Ben Straub

Merge pull request #1738 from libgit2/diff-patch-content-size

Add API for getting at git_diff_patch->content_size
parents bf145a6a eb1c1707
......@@ -391,7 +391,7 @@ typedef enum {
*/
GIT_DIFF_LINE_FILE_HDR = 'F',
GIT_DIFF_LINE_HUNK_HDR = 'H',
GIT_DIFF_LINE_BINARY = 'B'
GIT_DIFF_LINE_BINARY = 'B' /**< For "Binary files x and y differ" */
} git_diff_line_t;
/**
......@@ -943,6 +943,28 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
size_t line_of_hunk);
/**
* Look up size of patch diff data in bytes
*
* This returns the raw size of the patch data. This only includes the
* actual data from the lines of the diff, not the file or hunk headers.
*
* If you pass `include_context` as true (non-zero), this will be the size
* of all of the diff output; if you pass it as false (zero), this will
* only include the actual changed lines (as if `context_lines` was 0).
*
* @param patch A git_diff_patch representing changes to one file
* @param include_context Include context lines in size if non-zero
* @param include_hunk_headers Include hunk header lines if non-zero
* @param include_file_headers Include file header lines if non-zero
* @return The number of bytes of data
*/
GIT_EXTERN(size_t) git_diff_patch_size(
git_diff_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers);
/**
* Serialize the patch to text via callback.
*
* Returning a non-zero value from the callback will terminate the iteration
......
......@@ -82,6 +82,13 @@ extern const char *git_diff_delta__path(const git_diff_delta *delta);
extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta);
extern int git_diff_delta__format_file_header(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
int oid_strlen);
extern int git_diff__oid_for_file(
git_repository *, const char *, uint16_t, git_off_t, git_oid *);
......
......@@ -42,7 +42,7 @@ struct git_diff_patch {
git_array_t(diff_patch_hunk) hunks;
git_array_t(diff_patch_line) lines;
size_t oldno, newno;
size_t content_size;
size_t content_size, context_size, header_size;
git_pool flattened;
};
......@@ -810,6 +810,39 @@ notfound:
return diff_error_outofrange(thing);
}
size_t git_diff_patch_size(
git_diff_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers)
{
size_t out;
assert(patch);
out = patch->content_size;
if (!include_context)
out -= patch->context_size;
if (include_hunk_headers)
out += patch->header_size;
if (include_file_headers) {
git_buf file_header = GIT_BUF_INIT;
if (git_diff_delta__format_file_header(
&file_header, patch->delta, NULL, NULL, 0) < 0)
giterr_clear();
else
out += git_buf_len(&file_header);
git_buf_free(&file_header);
}
return out;
}
git_diff_list *git_diff_patch__diff(git_diff_patch *patch)
{
return patch->diff;
......@@ -904,6 +937,8 @@ static int diff_patch_hunk_cb(
hunk->header[header_len] = '\0';
hunk->header_len = header_len;
patch->header_size += header_len;
hunk->line_start = git_array_size(patch->lines);
hunk->line_count = 0;
......@@ -924,6 +959,7 @@ static int diff_patch_line_cb(
git_diff_patch *patch = payload;
diff_patch_hunk *hunk;
diff_patch_line *line;
const char *content_end = content + content_len;
GIT_UNUSED(delta);
GIT_UNUSED(range);
......@@ -938,34 +974,43 @@ static int diff_patch_line_cb(
line->len = content_len;
line->origin = line_origin;
patch->content_size += content_len;
/* do some bookkeeping so we can provide old/new line numbers */
for (line->lines = 0; content_len > 0; --content_len) {
line->lines = 0;
while (content < content_end)
if (*content++ == '\n')
++line->lines;
}
patch->content_size += content_len;
switch (line_origin) {
case GIT_DIFF_LINE_ADDITION:
patch->content_size += 1;
case GIT_DIFF_LINE_DEL_EOFNL:
line->oldno = -1;
line->newno = patch->newno;
patch->newno += line->lines;
break;
case GIT_DIFF_LINE_DELETION:
patch->content_size += 1;
case GIT_DIFF_LINE_ADD_EOFNL:
line->oldno = patch->oldno;
line->newno = -1;
patch->oldno += line->lines;
break;
default:
case GIT_DIFF_LINE_CONTEXT:
patch->content_size += 1;
patch->context_size += 1;
case GIT_DIFF_LINE_CONTEXT_EOFNL:
patch->context_size += content_len;
line->oldno = patch->oldno;
line->newno = patch->newno;
patch->oldno += line->lines;
patch->newno += line->lines;
break;
default:
assert(false);
break;
}
hunk->line_count++;
......
......@@ -98,12 +98,12 @@ static int diff_print_one_compact(
if (delta->old_file.path != delta->new_file.path &&
strcomp(delta->old_file.path,delta->new_file.path) != 0)
git_buf_printf(out, "%c\t%s%c -> %s%c\n", code,
git_buf_printf(out, "%c\t%s%c %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (delta->old_file.mode != delta->new_file.mode &&
delta->old_file.mode != 0 && delta->new_file.mode != 0)
git_buf_printf(out, "%c\t%s%c (%o -> %o)\n", code,
delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode);
git_buf_printf(out, "%c\t%s%c %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (old_suffix != ' ')
git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
else
......@@ -198,13 +198,13 @@ int git_diff_print_raw(
return error;
}
static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
static int diff_print_oid_range(
git_buf *out, const git_diff_delta *delta, int oid_strlen)
{
git_buf *out = pi->buf;
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid);
git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid);
/* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) {
......@@ -228,52 +228,78 @@ static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta
return 0;
}
static int diff_print_patch_file(
const git_diff_delta *delta, float progress, void *data)
static int diff_delta_format_with_paths(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
const char *template)
{
diff_print_info *pi = data;
const char *oldpfx = pi->diff ? pi->diff->opts.old_prefix : NULL;
const char *oldpath = delta->old_file.path;
const char *newpfx = pi->diff ? pi->diff->opts.new_prefix : NULL;
const char *newpath = delta->new_file.path;
uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL;
GIT_UNUSED(progress);
if (git_oid_iszero(&delta->old_file.oid)) {
oldpfx = "";
oldpath = "/dev/null";
}
if (git_oid_iszero(&delta->new_file.oid)) {
newpfx = "";
newpath = "/dev/null";
}
if (S_ISDIR(delta->new_file.mode) ||
delta->status == GIT_DELTA_UNMODIFIED ||
delta->status == GIT_DELTA_IGNORED ||
(delta->status == GIT_DELTA_UNTRACKED &&
(opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))
return 0;
return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
}
int git_diff_delta__format_file_header(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
int oid_strlen)
{
if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT;
if (!newpfx)
newpfx = DIFF_NEW_PREFIX_DEFAULT;
if (!oid_strlen)
oid_strlen = GIT_ABBREV_DEFAULT + 1;
git_buf_clear(pi->buf);
git_buf_printf(pi->buf, "diff --git %s%s %s%s\n",
git_buf_clear(out);
git_buf_printf(out, "diff --git %s%s %s%s\n",
oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
if (diff_print_oid_range(pi, delta) < 0)
if (diff_print_oid_range(out, delta, oid_strlen) < 0)
return -1;
if (git_oid_iszero(&delta->old_file.oid)) {
oldpfx = "";
oldpath = "/dev/null";
}
if (git_oid_iszero(&delta->new_file.oid)) {
newpfx = "";
newpath = "/dev/null";
}
if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
diff_delta_format_with_paths(
out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) {
git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);
git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);
}
return git_buf_oom(out) ? -1 : 0;
}
if (git_buf_oom(pi->buf))
static int diff_print_patch_file(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
const char *oldpfx =
pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *newpfx =
pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
uint32_t opts_flags = pi->diff ? pi->diff->opts.flags : GIT_DIFF_NORMAL;
GIT_UNUSED(progress);
if (S_ISDIR(delta->new_file.mode) ||
delta->status == GIT_DELTA_UNMODIFIED ||
delta->status == GIT_DELTA_IGNORED ||
(delta->status == GIT_DELTA_UNTRACKED &&
(opts_flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))
return 0;
if (git_diff_delta__format_file_header(
pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0)
return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
......@@ -284,10 +310,10 @@ static int diff_print_patch_file(
return 0;
git_buf_clear(pi->buf);
git_buf_printf(
pi->buf, "Binary files %s%s and %s%s differ\n",
oldpfx, oldpath, newpfx, newpath);
if (git_buf_oom(pi->buf))
if (diff_delta_format_with_paths(
pi->buf, delta, oldpfx, newpfx,
"Binary files %s%s and %s%s differ\n") < 0)
return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY,
......
......@@ -128,6 +128,11 @@ void test_diff_patch__to_string(void)
cl_assert_equal_s(expected, text);
cl_assert_equal_sz(31, git_diff_patch_size(patch, 0, 0, 0));
cl_assert_equal_sz(31, git_diff_patch_size(patch, 1, 0, 0));
cl_assert_equal_sz(31 + 16, git_diff_patch_size(patch, 1, 1, 0));
cl_assert_equal_sz(strlen(expected), git_diff_patch_size(patch, 1, 1, 1));
git__free(text);
git_diff_patch_free(patch);
git_diff_list_free(diff);
......@@ -408,7 +413,7 @@ void test_diff_patch__hunks_have_correct_line_numbers(void)
static void check_single_patch_stats(
git_repository *repo, size_t hunks,
size_t adds, size_t dels, size_t ctxt,
size_t adds, size_t dels, size_t ctxt, size_t *sizes,
const char *expected)
{
git_diff_list *diff;
......@@ -437,6 +442,18 @@ static void check_single_patch_stats(
cl_git_pass(git_diff_patch_to_str(&text, patch));
cl_assert_equal_s(expected, text);
git__free(text);
cl_assert_equal_sz(
strlen(expected), git_diff_patch_size(patch, 1, 1, 1));
}
if (sizes) {
if (sizes[0])
cl_assert_equal_sz(sizes[0], git_diff_patch_size(patch, 0, 0, 0));
if (sizes[1])
cl_assert_equal_sz(sizes[1], git_diff_patch_size(patch, 1, 0, 0));
if (sizes[2])
cl_assert_equal_sz(sizes[2], git_diff_patch_size(patch, 1, 1, 0));
}
/* walk lines in hunk with basic sanity checks */
......@@ -481,6 +498,23 @@ void test_diff_patch__line_counts_with_eofnl(void)
git_buf content = GIT_BUF_INIT;
const char *end;
git_index *index;
const char *expected =
/* below is pasted output of 'git diff' with fn context removed */
"diff --git a/songof7cities.txt b/songof7cities.txt\n"
"index 378a7d9..3d0154e 100644\n"
"--- a/songof7cities.txt\n"
"+++ b/songof7cities.txt\n"
"@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n"
" \n"
" To the sound of trumpets shall their seed restore my Cities\n"
" Wealthy and well-weaponed, that once more may I behold\n"
"-All the world go softly when it walks before my Cities,\n"
"+#All the world go softly when it walks before my Cities,\n"
" And the horses and the chariots fleeing from them as of old!\n"
" \n"
" -- Rudyard Kipling\n"
"\\ No newline at end of file\n";
size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 };
g_repo = cl_git_sandbox_init("renames");
......@@ -495,14 +529,14 @@ void test_diff_patch__line_counts_with_eofnl(void)
git_buf_consume(&content, end);
cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL);
check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL);
/* remove trailing whitespace */
git_buf_rtrim(&content);
cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL);
check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL);
/* add trailing whitespace */
......@@ -514,7 +548,7 @@ void test_diff_patch__line_counts_with_eofnl(void)
cl_git_pass(git_buf_putc(&content, '\n'));
cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL);
check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL);
/* no trailing whitespace as context line */
......@@ -537,22 +571,7 @@ void test_diff_patch__line_counts_with_eofnl(void)
cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
check_single_patch_stats(
g_repo, 1, 1, 1, 6,
/* below is pasted output of 'git diff' with fn context removed */
"diff --git a/songof7cities.txt b/songof7cities.txt\n"
"index 378a7d9..3d0154e 100644\n"
"--- a/songof7cities.txt\n"
"+++ b/songof7cities.txt\n"
"@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n"
" \n"
" To the sound of trumpets shall their seed restore my Cities\n"
" Wealthy and well-weaponed, that once more may I behold\n"
"-All the world go softly when it walks before my Cities,\n"
"+#All the world go softly when it walks before my Cities,\n"
" And the horses and the chariots fleeing from them as of old!\n"
" \n"
" -- Rudyard Kipling\n"
"\\ No newline at end of file\n");
g_repo, 1, 1, 1, 6, expected_sizes, expected);
git_buf_free(&content);
git_config_free(cfg);
......
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