Commit f9c824c5 by Russell Belfer

Add patch from blobs API

This adds two new public APIs: git_diff_patch_from_blobs and
git_diff_patch_from_blob_and_buffer, plus it refactors the code
for git_diff_blobs and git_diff_blob_to_buffer so that they code
is almost entirely shared between these APIs, and adds tests for
the new APIs.
parent 54faddd2
......@@ -860,7 +860,7 @@ GIT_EXTERN(size_t) git_diff_patch_num_hunks(
* @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
* @return 0 on success, <0 on error
*/
GIT_EXTERN(int) git_diff_patch_line_stats(
size_t *total_context,
......@@ -1001,6 +1001,26 @@ GIT_EXTERN(int) git_diff_blobs(
void *payload);
/**
* Directly generate a patch from the difference between two blobs.
*
* This is just like `git_diff_blobs()` except it generates a patch object
* for the difference instead of directly making callbacks. You can use the
* standard `git_diff_patch` accessor functions to read the patch data, and
* you must call `git_diff_patch_free()` on the patch when done.
*
* @param out The generated patch; NULL on error
* @param old_blob Blob for old side of diff, or NULL for empty blob
* @param new_blob Blob for new side of diff, or NULL for empty blob
* @param options Options for diff, or NULL for default options
* @return 0 on success or error code < 0
*/
GIT_EXTERN(int) git_diff_patch_from_blobs(
git_diff_patch **out,
const git_blob *old_blob,
const git_blob *new_blob,
const git_diff_options *opts);
/**
* Directly run a diff between a blob and a buffer.
*
* As with `git_diff_blobs`, comparing a blob and buffer lacks some context,
......@@ -1013,7 +1033,7 @@ GIT_EXTERN(int) git_diff_blobs(
* the reverse, with GIT_DELTA_REMOVED and blob content removed.
*
* @param old_blob Blob for old side of diff, or NULL for empty blob
* @param buffer Raw data for new side of diff
* @param buffer Raw data for new side of diff, or NULL for empty
* @param buffer_len Length of raw data for new side of diff
* @param options Options for diff, or NULL for default options
* @param file_cb Callback for "file"; made once if there is a diff; can be NULL
......@@ -1032,6 +1052,29 @@ GIT_EXTERN(int) git_diff_blob_to_buffer(
git_diff_data_cb data_cb,
void *payload);
/**
* Directly generate a patch from the difference between a blob and a buffer.
*
* This is just like `git_diff_blob_to_buffer()` except it generates a patch
* object for the difference instead of directly making callbacks. You can
* use the standard `git_diff_patch` accessor functions to read the patch
* data, and you must call `git_diff_patch_free()` on the patch when done.
*
* @param out The generated patch; NULL on error
* @param old_blob Blob for old side of diff, or NULL for empty blob
* @param buffer Raw data for new side of diff, or NULL for empty
* @param buffer_len Length of raw data for new side of diff
* @param options Options for diff, or NULL for default options
* @return 0 on success or error code < 0
*/
GIT_EXTERN(int) git_diff_patch_from_blob_and_buffer(
git_diff_patch **out,
const git_blob *old_blob,
const char *buf,
size_t buflen,
const git_diff_options *opts);
GIT_END_DECL
/** @} */
......
......@@ -265,33 +265,32 @@ int git_diff_foreach(
}
typedef struct {
git_xdiff_output xo;
git_diff_patch patch;
git_diff_delta delta;
} diff_single_info;
} diff_patch_with_delta;
static int diff_single_generate(diff_single_info *info)
static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
{
int error = 0;
git_diff_patch *patch = &info->patch;
git_diff_patch *patch = &pd->patch;
bool has_old = ((patch->ofile.file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
bool has_new = ((patch->nfile.file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
info->delta.status = has_new ?
pd->delta.status = has_new ?
(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
if (git_oid_equal(&patch->nfile.file.oid, &patch->ofile.file.oid))
info->delta.status = GIT_DELTA_UNMODIFIED;
pd->delta.status = GIT_DELTA_UNMODIFIED;
patch->delta = &info->delta;
patch->delta = &pd->delta;
diff_patch_init_common(patch);
error = diff_patch_file_callback(patch, (git_diff_output *)&info->xo);
error = diff_patch_file_callback(patch, (git_diff_output *)xo);
if (!error)
error = diff_patch_generate(patch, (git_diff_output *)&info->xo);
error = diff_patch_generate(patch, (git_diff_output *)xo);
if (error == GIT_EUSER)
giterr_clear(); /* don't leave error message set invalidly */
......@@ -299,24 +298,23 @@ static int diff_single_generate(diff_single_info *info)
return error;
}
int git_diff_blobs(
static int diff_patch_from_blobs(
diff_patch_with_delta *pd,
git_xdiff_output *xo,
const git_blob *old_blob,
const git_blob *new_blob,
const git_diff_options *opts,
git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb,
git_diff_data_cb data_cb,
void *payload)
const git_diff_options *opts)
{
int error = 0;
diff_single_info info;
git_repository *repo =
new_blob ? git_object_owner((const git_object *)new_blob) :
old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
if (!repo) /* Hmm, given two NULL blobs, silently do no callbacks? */
pd->patch.delta = &pd->delta;
if (!repo) /* return two NULL items as UNMODIFIED delta */
return 0;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
......@@ -325,64 +323,163 @@ int git_diff_blobs(
new_blob = swap;
}
memset(&info, 0, sizeof(info));
if ((error = diff_file_content_init_from_blob(
&pd->patch.ofile, repo, opts, old_blob)) < 0 ||
(error = diff_file_content_init_from_blob(
&pd->patch.nfile, repo, opts, new_blob)) < 0)
return error;
diff_output_init((git_diff_output *)&info.xo,
opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&info.xo, opts);
return diff_single_generate(pd, xo);
}
if (!(error = diff_file_content_init_from_blob(
&info.patch.ofile, repo, opts, old_blob)) &&
!(error = diff_file_content_init_from_blob(
&info.patch.nfile, repo, opts, new_blob)))
error = diff_single_generate(&info);
int git_diff_blobs(
const git_blob *old_blob,
const git_blob *new_blob,
const git_diff_options *opts,
git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb,
git_diff_data_cb data_cb,
void *payload)
{
int error = 0;
diff_patch_with_delta pd;
git_xdiff_output xo;
git_diff_patch_free(&info.patch);
memset(&pd, 0, sizeof(pd));
memset(&xo, 0, sizeof(xo));
diff_output_init(
(git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, opts);
error = diff_patch_from_blobs(&pd, &xo, old_blob, new_blob, opts);
git_diff_patch_free((git_diff_patch *)&pd);
return error;
}
int git_diff_blob_to_buffer(
int git_diff_patch_from_blobs(
git_diff_patch **out,
const git_blob *old_blob,
const git_blob *new_blob,
const git_diff_options *opts)
{
int error = 0;
diff_patch_with_delta *pd;
git_xdiff_output xo;
assert(out);
*out = NULL;
pd = git__calloc(1, sizeof(*pd));
GITERR_CHECK_ALLOC(pd);
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
memset(&xo, 0, sizeof(xo));
diff_output_to_patch((git_diff_output *)&xo, &pd->patch);
git_xdiff_init(&xo, opts);
if (!(error = diff_patch_from_blobs(pd, &xo, old_blob, new_blob, opts)))
*out = (git_diff_patch *)pd;
else
git_diff_patch_free((git_diff_patch *)pd);
return error;
}
static int diff_patch_from_blob_and_buffer(
diff_patch_with_delta *pd,
git_xdiff_output *xo,
const git_blob *old_blob,
const char *buf,
size_t buflen,
const git_diff_options *opts,
git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb,
git_diff_data_cb data_cb,
void *payload)
const git_diff_options *opts)
{
int error = 0;
diff_single_info info;
git_repository *repo =
old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
if (!repo && !buf) /* Hmm, given NULLs, silently do no callbacks? */
return 0;
memset(&info, 0, sizeof(info));
pd->patch.delta = &pd->delta;
diff_output_init((git_diff_output *)&info.xo,
opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&info.xo, opts);
if (!repo && !buf) /* return two NULL items as UNMODIFIED delta */
return 0;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
if (!(error = diff_file_content_init_from_raw(
&info.patch.ofile, repo, opts, buf, buflen)))
&pd->patch.ofile, repo, opts, buf, buflen)))
error = diff_file_content_init_from_blob(
&info.patch.nfile, repo, opts, old_blob);
&pd->patch.nfile, repo, opts, old_blob);
} else {
if (!(error = diff_file_content_init_from_blob(
&info.patch.ofile, repo, opts, old_blob)))
&pd->patch.ofile, repo, opts, old_blob)))
error = diff_file_content_init_from_raw(
&info.patch.nfile, repo, opts, buf, buflen);
&pd->patch.nfile, repo, opts, buf, buflen);
}
error = diff_single_generate(&info);
return diff_single_generate(pd, xo);
}
int git_diff_blob_to_buffer(
const git_blob *old_blob,
const char *buf,
size_t buflen,
const git_diff_options *opts,
git_diff_file_cb file_cb,
git_diff_hunk_cb hunk_cb,
git_diff_data_cb data_cb,
void *payload)
{
int error = 0;
diff_patch_with_delta pd;
git_xdiff_output xo;
git_diff_patch_free(&info.patch);
memset(&pd, 0, sizeof(pd));
memset(&xo, 0, sizeof(xo));
diff_output_init(
(git_diff_output *)&xo, opts, file_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, opts);
error = diff_patch_from_blob_and_buffer(
&pd, &xo, old_blob, buf, buflen, opts);
git_diff_patch_free((git_diff_patch *)&pd);
return error;
}
int git_diff_patch_from_blob_and_buffer(
git_diff_patch **out,
const git_blob *old_blob,
const char *buf,
size_t buflen,
const git_diff_options *opts)
{
int error = 0;
diff_patch_with_delta *pd;
git_xdiff_output xo;
assert(out);
*out = NULL;
pd = git__calloc(1, sizeof(*pd));
GITERR_CHECK_ALLOC(pd);
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
memset(&xo, 0, sizeof(xo));
diff_output_to_patch((git_diff_output *)&xo, &pd->patch);
git_xdiff_init(&xo, opts);
if (!(error = diff_patch_from_blob_and_buffer(
pd, &xo, old_blob, buf, buflen, opts)))
*out = (git_diff_patch *)pd;
else
git_diff_patch_free((git_diff_patch *)pd);
return error;
}
......@@ -599,9 +696,7 @@ static int diff_patch_file_cb(
float progress,
void *payload)
{
GIT_UNUSED(delta);
GIT_UNUSED(progress);
GIT_UNUSED(payload);
GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
return 0;
}
......
......@@ -120,6 +120,93 @@ void test_diff_blob__can_compare_text_blobs(void)
git_blob_free(c);
}
void test_diff_blob__can_compare_text_blobs_with_patch(void)
{
git_blob *a, *b, *c;
git_oid a_oid, b_oid, c_oid;
git_diff_patch *p;
size_t tc, ta, td;
/* tests/resources/attr/root_test1 */
cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8));
cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4));
/* tests/resources/attr/root_test2 */
cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8));
cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4));
/* tests/resources/attr/root_test3 */
cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16));
cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 8));
/* Doing the equivalent of a `git diff -U1` on these files */
/* diff on tests/resources/attr/root_test1 */
cl_git_pass(git_diff_patch_from_blobs(&p, a, b, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(6, git_diff_patch_num_lines_in_hunk(p, 0));
cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p));
cl_assert_equal_i(1, (int)tc);
cl_assert_equal_i(5, (int)ta);
cl_assert_equal_i(0, (int)td);
git_diff_patch_free(p);
/* diff on tests/resources/attr/root_test2 */
cl_git_pass(git_diff_patch_from_blobs(&p, b, c, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(15, git_diff_patch_num_lines_in_hunk(p, 0));
cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p));
cl_assert_equal_i(3, (int)tc);
cl_assert_equal_i(9, (int)ta);
cl_assert_equal_i(3, (int)td);
git_diff_patch_free(p);
/* diff on tests/resources/attr/root_test3 */
cl_git_pass(git_diff_patch_from_blobs(&p, a, c, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(13, git_diff_patch_num_lines_in_hunk(p, 0));
cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p));
cl_assert_equal_i(0, (int)tc);
cl_assert_equal_i(12, (int)ta);
cl_assert_equal_i(1, (int)td);
git_diff_patch_free(p);
/* one more */
cl_git_pass(git_diff_patch_from_blobs(&p, c, d, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(2, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(5, git_diff_patch_num_lines_in_hunk(p, 0));
cl_assert_equal_i(9, git_diff_patch_num_lines_in_hunk(p, 1));
cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p));
cl_assert_equal_i(4, (int)tc);
cl_assert_equal_i(6, (int)ta);
cl_assert_equal_i(4, (int)td);
git_diff_patch_free(p);
git_blob_free(a);
git_blob_free(b);
git_blob_free(c);
}
void test_diff_blob__can_compare_against_null_blobs(void)
{
git_blob *e = NULL;
......@@ -175,6 +262,66 @@ void test_diff_blob__can_compare_against_null_blobs(void)
cl_assert_equal_i(0, expected.lines);
}
void test_diff_blob__can_compare_against_null_blobs_with_patch(void)
{
git_blob *e = NULL;
git_diff_patch *p;
int line;
char origin;
cl_git_pass(git_diff_patch_from_blobs(&p, d, e, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_DELETED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(14, git_diff_patch_num_lines_in_hunk(p, 0));
for (line = 0; line < git_diff_patch_num_lines_in_hunk(p, 0); ++line) {
cl_git_pass(git_diff_patch_get_line_in_hunk(
&origin, NULL, NULL, NULL, NULL, p, 0, line));
cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)origin);
}
git_diff_patch_free(p);
opts.flags |= GIT_DIFF_REVERSE;
cl_git_pass(git_diff_patch_from_blobs(&p, d, e, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(14, git_diff_patch_num_lines_in_hunk(p, 0));
for (line = 0; line < git_diff_patch_num_lines_in_hunk(p, 0); ++line) {
cl_git_pass(git_diff_patch_get_line_in_hunk(
&origin, NULL, NULL, NULL, NULL, p, 0, line));
cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)origin);
}
git_diff_patch_free(p);
opts.flags ^= GIT_DIFF_REVERSE;
cl_git_pass(git_diff_patch_from_blobs(&p, alien, NULL, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_DELETED, git_diff_patch_delta(p)->status);
cl_assert((git_diff_patch_delta(p)->flags & GIT_DIFF_FLAG_BINARY) != 0);
cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p));
git_diff_patch_free(p);
cl_git_pass(git_diff_patch_from_blobs(&p, NULL, alien, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status);
cl_assert((git_diff_patch_delta(p)->flags & GIT_DIFF_FLAG_BINARY) != 0);
cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p));
git_diff_patch_free(p);
}
static void assert_identical_blobs_comparison(diff_expects *expected)
{
cl_assert_equal_i(1, expected->files);
......@@ -206,6 +353,29 @@ void test_diff_blob__can_compare_identical_blobs(void)
assert_identical_blobs_comparison(&expected);
}
void test_diff_blob__can_compare_identical_blobs_with_patch(void)
{
git_diff_patch *p;
cl_git_pass(git_diff_patch_from_blobs(&p, d, d, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p));
git_diff_patch_free(p);
cl_git_pass(git_diff_patch_from_blobs(&p, NULL, NULL, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p));
git_diff_patch_free(p);
cl_git_pass(git_diff_patch_from_blobs(&p, alien, alien, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p));
git_diff_patch_free(p);
}
static void assert_binary_blobs_comparison(diff_expects *expected)
{
cl_assert(expected->files_binary > 0);
......@@ -428,6 +598,74 @@ void test_diff_blob__can_compare_blob_to_buffer(void)
git_blob_free(a);
}
void test_diff_blob__can_compare_blob_to_buffer_with_patch(void)
{
git_diff_patch *p;
git_blob *a;
git_oid a_oid;
const char *a_content = "Hello from the root\n";
const char *b_content = "Hello from the root\n\nSome additional lines\n\nDown here below\n\n";
size_t tc, ta, td;
/* tests/resources/attr/root_test1 */
cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8));
cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4));
/* diff from blob a to content of b */
cl_git_pass(git_diff_patch_from_blob_and_buffer(
&p, a, b_content, strlen(b_content), &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_MODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(6, git_diff_patch_num_lines_in_hunk(p, 0));
cl_git_pass(git_diff_patch_line_stats(&tc, &ta, &td, p));
cl_assert_equal_i(1, (int)tc);
cl_assert_equal_i(5, (int)ta);
cl_assert_equal_i(0, (int)td);
git_diff_patch_free(p);
/* diff from blob a to content of a */
cl_git_pass(git_diff_patch_from_blob_and_buffer(
&p, a, a_content, strlen(a_content), &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(0, (int)git_diff_patch_num_hunks(p));
git_diff_patch_free(p);
/* diff from NULL blob to content of a */
cl_git_pass(git_diff_patch_from_blob_and_buffer(
&p, NULL, a_content, strlen(a_content), &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(1, git_diff_patch_num_lines_in_hunk(p, 0));
git_diff_patch_free(p);
/* diff from blob a to NULL buffer */
cl_git_pass(git_diff_patch_from_blob_and_buffer(
&p, a, NULL, 0, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_DELETED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(1, git_diff_patch_num_lines_in_hunk(p, 0));
git_diff_patch_free(p);
/* diff with reverse */
opts.flags ^= GIT_DIFF_REVERSE;
cl_git_pass(git_diff_patch_from_blob_and_buffer(
&p, a, NULL, 0, &opts));
cl_assert(p != NULL);
cl_assert_equal_i(GIT_DELTA_ADDED, git_diff_patch_delta(p)->status);
cl_assert_equal_i(1, (int)git_diff_patch_num_hunks(p));
cl_assert_equal_i(1, git_diff_patch_num_lines_in_hunk(p, 0));
git_diff_patch_free(p);
git_blob_free(a);
}
static void assert_one_modified_with_lines(diff_expects *expected, int lines)
{
......
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