]> git.proxmox.com Git - libgit2.git/commitdiff
Add buffer to buffer diff and patch APIs
authorRussell Belfer <rb@github.com>
Thu, 27 Feb 2014 22:13:22 +0000 (14:13 -0800)
committerRussell Belfer <rb@github.com>
Thu, 27 Feb 2014 22:13:22 +0000 (14:13 -0800)
This adds `git_diff_buffers` and `git_patch_from_buffers`.  This
also includes a bunch of internal refactoring to increase the
shared code between these functions and the blob-to-blob and
blob-to-buffer APIs, as well as some higher level assert helpers
in the tests to also remove redundancy.

include/git2/diff.h
include/git2/patch.h
src/diff_file.c
src/diff_file.h
src/diff_patch.c
tests/diff/blob.c

index a3cdd8193e900495b3cab942ab171818bc42b6c3..943e2ec4cde9a3d1070f546f5a28d6d631f59776 100644 (file)
@@ -1013,6 +1013,39 @@ GIT_EXTERN(int) git_diff_blob_to_buffer(
        git_diff_line_cb line_cb,
        void *payload);
 
+/**
+ * Directly run a diff between two buffers.
+ *
+ * Even more than with `git_diff_blobs`, comparing two buffer lacks
+ * context, so the `git_diff_file` parameters to the callbacks will be
+ * faked a la the rules for `git_diff_blobs()`.
+ *
+ * @param old_buffer Raw data for old side of diff, or NULL for empty
+ * @param old_len Length of the raw data for old side of the diff
+ * @param old_as_path Treat old buffer as if it had this filename; can be NULL
+ * @param new_buffer Raw data for new side of diff, or NULL for empty
+ * @param new_len Length of raw data for new side of diff
+ * @param new_as_path Treat buffer as if it had this filename; can be NULL
+ * @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
+ * @param hunk_cb Callback for each hunk in diff; can be NULL
+ * @param line_cb Callback for each line in diff; can be NULL
+ * @param payload Payload passed to each callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_buffers(
+       const void *old_buffer,
+       size_t old_len,
+       const char *old_as_path,
+       const void *new_buffer,
+       size_t new_len,
+       const char *new_as_path,
+       const git_diff_options *options,
+       git_diff_file_cb file_cb,
+       git_diff_hunk_cb hunk_cb,
+       git_diff_line_cb line_cb,
+       void *payload);
+
 
 GIT_END_DECL
 
index 1eca29d4ad2f3c43738dcd707d71fd026f287166..f5ec682c628043fa5add967589a6d26045fa76fd 100644 (file)
@@ -105,6 +105,34 @@ GIT_EXTERN(int) git_patch_from_blob_and_buffer(
        const char *buffer_as_path,
        const git_diff_options *opts);
 
+/**
+ * Directly generate a patch from the difference between two buffers.
+ *
+ * This is just like `git_diff_buffers()` except it generates a patch
+ * object for the difference instead of directly making callbacks.  You can
+ * use the standard `git_patch` accessor functions to read the patch
+ * data, and you must call `git_patch_free()` on the patch when done.
+ *
+ * @param out The generated patch; NULL on error
+ * @param old_buffer Raw data for old side of diff, or NULL for empty
+ * @param old_len Length of the raw data for old side of the diff
+ * @param old_as_path Treat old buffer as if it had this filename; can be NULL
+ * @param new_buffer Raw data for new side of diff, or NULL for empty
+ * @param new_len Length of raw data for new side of diff
+ * @param new_as_path Treat buffer as if it had this filename; can be NULL
+ * @param opts Options for diff, or NULL for default options
+ * @return 0 on success or error code < 0
+ */
+GIT_EXTERN(int) git_patch_from_buffers(
+       git_patch **out,
+       const void *old_buffer,
+       size_t old_len,
+       const char *old_as_path,
+       const char *new_buffer,
+       size_t new_len,
+       const char *new_as_path,
+       const git_diff_options *opts);
+
 /**
  * Free a git_patch object.
  */
index fb5d674f741edc1af1638e42b374c7bbfd457659..7dabf8d6fe1d724536930d1a9daf87284a8872ce 100644 (file)
@@ -127,57 +127,38 @@ int git_diff_file_content__init_from_diff(
        return diff_file_content_init_common(fc, &diff->opts);
 }
 
-int git_diff_file_content__init_from_blob(
+int git_diff_file_content__init_from_src(
        git_diff_file_content *fc,
        git_repository *repo,
        const git_diff_options *opts,
-       const git_blob *blob,
+       const git_diff_file_content_src *src,
        git_diff_file *as_file)
 {
        memset(fc, 0, sizeof(*fc));
        fc->repo = repo;
        fc->file = as_file;
-       fc->blob = blob;
+       fc->blob = src->blob;
 
-       if (!blob) {
+       if (!src->blob && !src->buf) {
                fc->flags |= GIT_DIFF_FLAG__NO_DATA;
        } else {
                fc->flags |= GIT_DIFF_FLAG__LOADED;
                fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
-               fc->file->size = git_blob_rawsize(blob);
                fc->file->mode = GIT_FILEMODE_BLOB;
-               git_oid_cpy(&fc->file->id, git_blob_id(blob));
 
-               fc->map.len  = (size_t)fc->file->size;
-               fc->map.data = (char *)git_blob_rawcontent(blob);
-       }
+               if (src->blob) {
+                       fc->file->size = git_blob_rawsize(src->blob);
+                       git_oid_cpy(&fc->file->id, git_blob_id(src->blob));
 
-       return diff_file_content_init_common(fc, opts);
-}
+                       fc->map.len  = (size_t)fc->file->size;
+                       fc->map.data = (char *)git_blob_rawcontent(src->blob);
+               } else {
+                       fc->file->size = src->buflen;
+                       git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB);
 
-int git_diff_file_content__init_from_raw(
-       git_diff_file_content *fc,
-       git_repository *repo,
-       const git_diff_options *opts,
-       const char *buf,
-       size_t buflen,
-       git_diff_file *as_file)
-{
-       memset(fc, 0, sizeof(*fc));
-       fc->repo = repo;
-       fc->file = as_file;
-
-       if (!buf) {
-               fc->flags |= GIT_DIFF_FLAG__NO_DATA;
-       } else {
-               fc->flags |= GIT_DIFF_FLAG__LOADED;
-               fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
-               fc->file->size = buflen;
-               fc->file->mode = GIT_FILEMODE_BLOB;
-               git_odb_hash(&fc->file->id, buf, buflen, GIT_OBJ_BLOB);
-
-               fc->map.len  = buflen;
-               fc->map.data = (char *)buf;
+                       fc->map.len  = src->buflen;
+                       fc->map.data = (char *)src->buf;
+               }
        }
 
        return diff_file_content_init_common(fc, opts);
index 84bf255aa5026a95c6cdcd6216bc27892ff295e8..4d290ad43008094fbc6edb584a2e28bd7b9810ef 100644 (file)
@@ -31,19 +31,21 @@ extern int git_diff_file_content__init_from_diff(
        size_t delta_index,
        bool use_old);
 
-extern int git_diff_file_content__init_from_blob(
-       git_diff_file_content *fc,
-       git_repository *repo,
-       const git_diff_options *opts,
-       const git_blob *blob,
-       git_diff_file *as_file);
+typedef struct {
+       const git_blob *blob;
+       const void *buf;
+       size_t buflen;
+       const char *as_path;
+} git_diff_file_content_src;
+
+#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) }
+#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) }
 
-extern int git_diff_file_content__init_from_raw(
+extern int git_diff_file_content__init_from_src(
        git_diff_file_content *fc,
        git_repository *repo,
        const git_diff_options *opts,
-       const char *buf,
-       size_t buflen,
+       const git_diff_file_content_src *src,
        git_diff_file *as_file);
 
 /* this loads the blob/file-on-disk as needed */
index ecae3a8eda62d1fe49265a518c48547f5156b4d2..dd8b73938f43fee04ce2eadb1ef14263f31771e2 100644 (file)
@@ -5,6 +5,7 @@
  * a Linking Exception. For full terms see the included COPYING file.
  */
 #include "common.h"
+#include "git2/blob.h"
 #include "diff.h"
 #include "diff_file.h"
 #include "diff_driver.h"
@@ -334,38 +335,45 @@ static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
        return error;
 }
 
-static int diff_patch_from_blobs(
+static int diff_patch_from_sources(
        diff_patch_with_delta *pd,
        git_xdiff_output *xo,
-       const git_blob *old_blob,
-       const char *old_path,
-       const git_blob *new_blob,
-       const char *new_path,
+       git_diff_file_content_src *oldsrc,
+       git_diff_file_content_src *newsrc,
        const git_diff_options *opts)
 {
        int error = 0;
        git_repository *repo =
-               new_blob ? git_object_owner((const git_object *)new_blob) :
-               old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
+               oldsrc->blob ? git_blob_owner(oldsrc->blob) :
+               newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
+       git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
+       git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
 
        GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
 
        if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
-               const git_blob *tmp_blob;
-               const char *tmp_path;
-               tmp_blob = old_blob; old_blob = new_blob; new_blob = tmp_blob;
-               tmp_path = old_path; old_path = new_path; new_path = tmp_path;
+               void *tmp = lfile; lfile = rfile; rfile = tmp;
+               tmp = ldata; ldata = rdata; rdata = tmp;
        }
 
        pd->patch.delta = &pd->delta;
 
-       pd->delta.old_file.path = old_path;
-       pd->delta.new_file.path = new_path;
+       if (!oldsrc->as_path) {
+               if (newsrc->as_path)
+                       oldsrc->as_path = newsrc->as_path;
+               else
+                       oldsrc->as_path = newsrc->as_path = "file";
+       }
+       else if (!newsrc->as_path)
+               newsrc->as_path = oldsrc->as_path;
+
+       lfile->path = oldsrc->as_path;
+       rfile->path = newsrc->as_path;
 
-       if ((error = git_diff_file_content__init_from_blob(
-                       &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)) < 0 ||
-               (error = git_diff_file_content__init_from_blob(
-                       &pd->patch.nfile, repo, opts, new_blob, &pd->delta.new_file)) < 0)
+       if ((error = git_diff_file_content__init_from_src(
+                       ldata, repo, opts, oldsrc, lfile)) < 0 ||
+               (error = git_diff_file_content__init_from_src(
+                       rdata, repo, opts, newsrc, rfile)) < 0)
                return error;
 
        return diff_single_generate(pd, xo);
@@ -400,11 +408,9 @@ static int diff_patch_with_delta_alloc(
        return 0;
 }
 
-int git_diff_blobs(
-       const git_blob *old_blob,
-       const char *old_path,
-       const git_blob *new_blob,
-       const char *new_path,
+static int diff_from_sources(
+       git_diff_file_content_src *oldsrc,
+       git_diff_file_content_src *newsrc,
        const git_diff_options *opts,
        git_diff_file_cb file_cb,
        git_diff_hunk_cb hunk_cb,
@@ -420,26 +426,19 @@ int git_diff_blobs(
                &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
        git_xdiff_init(&xo, opts);
 
-       if (!old_path && new_path)
-               old_path = new_path;
-       else if (!new_path && old_path)
-               new_path = old_path;
-
        memset(&pd, 0, sizeof(pd));
-       error = diff_patch_from_blobs(
-               &pd, &xo, old_blob, old_path, new_blob, new_path, opts);
+
+       error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts);
 
        git_patch_free(&pd.patch);
 
        return error;
 }
 
-int git_patch_from_blobs(
+static int patch_from_sources(
        git_patch **out,
-       const git_blob *old_blob,
-       const char *old_path,
-       const git_blob *new_blob,
-       const char *new_path,
+       git_diff_file_content_src *oldsrc,
+       git_diff_file_content_src *newsrc,
        const git_diff_options *opts)
 {
        int error = 0;
@@ -449,17 +448,15 @@ int git_patch_from_blobs(
        assert(out);
        *out = NULL;
 
-       if (diff_patch_with_delta_alloc(&pd, &old_path, &new_path) < 0)
-               return -1;
+       if ((error = diff_patch_with_delta_alloc(
+                       &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
+               return error;
 
        memset(&xo, 0, sizeof(xo));
        diff_output_to_patch(&xo.output, &pd->patch);
        git_xdiff_init(&xo, opts);
 
-       error = diff_patch_from_blobs(
-               pd, &xo, old_blob, old_path, new_blob, new_path, opts);
-
-       if (!error)
+       if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts)))
                *out = (git_patch *)pd;
        else
                git_patch_free((git_patch *)pd);
@@ -467,46 +464,38 @@ int git_patch_from_blobs(
        return error;
 }
 
-static int diff_patch_from_blob_and_buffer(
-       diff_patch_with_delta *pd,
-       git_xdiff_output *xo,
+int git_diff_blobs(
        const git_blob *old_blob,
        const char *old_path,
-       const char *buf,
-       size_t buflen,
-       const char *buf_path,
-       const git_diff_options *opts)
+       const git_blob *new_blob,
+       const char *new_path,
+       const git_diff_options *opts,
+       git_diff_file_cb file_cb,
+       git_diff_hunk_cb hunk_cb,
+       git_diff_line_cb data_cb,
+       void *payload)
 {
-       int error = 0;
-       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");
-
-       pd->patch.delta = &pd->delta;
-
-       if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
-               pd->delta.old_file.path = buf_path;
-               pd->delta.new_file.path = old_path;
-
-               if (!(error = git_diff_file_content__init_from_raw(
-                               &pd->patch.ofile, repo, opts, buf, buflen, &pd->delta.old_file)))
-                       error = git_diff_file_content__init_from_blob(
-                               &pd->patch.nfile, repo, opts, old_blob, &pd->delta.new_file);
-       } else {
-               pd->delta.old_file.path = old_path;
-               pd->delta.new_file.path = buf_path;
-
-               if (!(error = git_diff_file_content__init_from_blob(
-                               &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)))
-                       error = git_diff_file_content__init_from_raw(
-                               &pd->patch.nfile, repo, opts, buf, buflen, &pd->delta.new_file);
-       }
-
-       if (error < 0)
-               return error;
+       git_diff_file_content_src osrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+       git_diff_file_content_src nsrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
+       return diff_from_sources(
+               &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
+}
 
-       return diff_single_generate(pd, xo);
+int git_patch_from_blobs(
+       git_patch **out,
+       const git_blob *old_blob,
+       const char *old_path,
+       const git_blob *new_blob,
+       const char *new_path,
+       const git_diff_options *opts)
+{
+       git_diff_file_content_src osrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+       git_diff_file_content_src nsrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
+       return patch_from_sources(out, &osrc, &nsrc, opts);
 }
 
 int git_diff_blob_to_buffer(
@@ -521,27 +510,12 @@ int git_diff_blob_to_buffer(
        git_diff_line_cb data_cb,
        void *payload)
 {
-       int error = 0;
-       diff_patch_with_delta pd;
-       git_xdiff_output xo;
-
-       memset(&xo, 0, sizeof(xo));
-       diff_output_init(
-               &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
-       git_xdiff_init(&xo, opts);
-
-       if (!old_path && buf_path)
-               old_path = buf_path;
-       else if (!buf_path && old_path)
-               buf_path = old_path;
-
-       memset(&pd, 0, sizeof(pd));
-       error = diff_patch_from_blob_and_buffer(
-               &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
-
-       git_patch_free(&pd.patch);
-
-       return error;
+       git_diff_file_content_src osrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+       git_diff_file_content_src nsrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
+       return diff_from_sources(
+               &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
 }
 
 int git_patch_from_blob_and_buffer(
@@ -553,29 +527,49 @@ int git_patch_from_blob_and_buffer(
        const char *buf_path,
        const git_diff_options *opts)
 {
-       int error = 0;
-       diff_patch_with_delta *pd;
-       git_xdiff_output xo;
-
-       assert(out);
-       *out = NULL;
-
-       if (diff_patch_with_delta_alloc(&pd, &old_path, &buf_path) < 0)
-               return -1;
-
-       memset(&xo, 0, sizeof(xo));
-       diff_output_to_patch(&xo.output, &pd->patch);
-       git_xdiff_init(&xo, opts);
-
-       error = diff_patch_from_blob_and_buffer(
-               pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
+       git_diff_file_content_src osrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+       git_diff_file_content_src nsrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
+       return patch_from_sources(out, &osrc, &nsrc, opts);
+}
 
-       if (!error)
-               *out = (git_patch *)pd;
-       else
-               git_patch_free((git_patch *)pd);
+int git_diff_buffers(
+       const void *old_buf,
+       size_t old_len,
+       const char *old_path,
+       const void *new_buf,
+       size_t new_len,
+       const char *new_path,
+       const git_diff_options *opts,
+       git_diff_file_cb file_cb,
+       git_diff_hunk_cb hunk_cb,
+       git_diff_line_cb data_cb,
+       void *payload)
+{
+       git_diff_file_content_src osrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
+       git_diff_file_content_src nsrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
+       return diff_from_sources(
+               &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
+}
 
-       return error;
+int git_patch_from_buffers(
+       git_patch **out,
+       const void *old_buf,
+       size_t old_len,
+       const char *old_path,
+       const char *new_buf,
+       size_t new_len,
+       const char *new_path,
+       const git_diff_options *opts)
+{
+       git_diff_file_content_src osrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
+       git_diff_file_content_src nsrc =
+               GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
+       return patch_from_sources(out, &osrc, &nsrc, opts);
 }
 
 int git_patch_from_diff(
index 74df3cf85ef1b9d84b507fffd953e5ba70b016f4..d1fff9c5b472184a742fc5e62e92d05ad438cfe6 100644 (file)
@@ -51,6 +51,20 @@ void test_diff_blob__cleanup(void)
        cl_git_sandbox_cleanup();
 }
 
+static void assert_one_modified(
+       int hunks, int lines, int ctxt, int adds, int dels, diff_expects *exp)
+{
+       cl_assert_equal_i(1, exp->files);
+       cl_assert_equal_i(1, exp->file_status[GIT_DELTA_MODIFIED]);
+       cl_assert_equal_i(0, exp->files_binary);
+
+       cl_assert_equal_i(hunks, exp->hunks);
+       cl_assert_equal_i(lines, exp->lines);
+       cl_assert_equal_i(ctxt,  exp->line_ctxt);
+       cl_assert_equal_i(adds,  exp->line_adds);
+       cl_assert_equal_i(dels,  exp->line_dels);
+}
+
 void test_diff_blob__can_compare_text_blobs(void)
 {
        git_blob *a, *b, *c;
@@ -71,79 +85,81 @@ void test_diff_blob__can_compare_text_blobs(void)
        /* Doing the equivalent of a `git diff -U1` on these files */
 
        /* diff on tests/resources/attr/root_test1 */
+       memset(&expected, 0, sizeof(expected));
        cl_git_pass(git_diff_blobs(
                a, NULL, b, NULL, &opts,
                diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+       assert_one_modified(1, 6, 1, 5, 0, &expected);
 
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-
-       cl_assert_equal_i(1, expected.hunks);
-       cl_assert_equal_i(6, expected.lines);
-       cl_assert_equal_i(1, expected.line_ctxt);
-       cl_assert_equal_i(5, expected.line_adds);
-       cl_assert_equal_i(0, expected.line_dels);
+       /* same diff but use direct buffers */
+       memset(&expected, 0, sizeof(expected));
+       cl_git_pass(git_diff_buffers(
+               git_blob_rawcontent(a), (size_t)git_blob_rawsize(a), NULL,
+               git_blob_rawcontent(b), (size_t)git_blob_rawsize(b), NULL, &opts,
+               diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+       assert_one_modified(1, 6, 1, 5, 0, &expected);
 
        /* diff on tests/resources/attr/root_test2 */
        memset(&expected, 0, sizeof(expected));
        cl_git_pass(git_diff_blobs(
                b, NULL, c, NULL, &opts,
                diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
-
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-
-       cl_assert_equal_i(1, expected.hunks);
-       cl_assert_equal_i(15, expected.lines);
-       cl_assert_equal_i(3, expected.line_ctxt);
-       cl_assert_equal_i(9, expected.line_adds);
-       cl_assert_equal_i(3, expected.line_dels);
+       assert_one_modified(1, 15, 3, 9, 3, &expected);
 
        /* diff on tests/resources/attr/root_test3 */
        memset(&expected, 0, sizeof(expected));
        cl_git_pass(git_diff_blobs(
                a, NULL, c, NULL, &opts,
                diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
-
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-
-       cl_assert_equal_i(1, expected.hunks);
-       cl_assert_equal_i(13, expected.lines);
-       cl_assert_equal_i(0, expected.line_ctxt);
-       cl_assert_equal_i(12, expected.line_adds);
-       cl_assert_equal_i(1, expected.line_dels);
+       assert_one_modified(1, 13, 0, 12, 1, &expected);
 
        memset(&expected, 0, sizeof(expected));
        cl_git_pass(git_diff_blobs(
                c, NULL, d, NULL, &opts,
                diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
-
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-
-       cl_assert_equal_i(2, expected.hunks);
-       cl_assert_equal_i(14, expected.lines);
-       cl_assert_equal_i(4, expected.line_ctxt);
-       cl_assert_equal_i(6, expected.line_adds);
-       cl_assert_equal_i(4, expected.line_dels);
+       assert_one_modified(2, 14, 4, 6, 4, &expected);
 
        git_blob_free(a);
        git_blob_free(b);
        git_blob_free(c);
 }
 
+static void assert_patch_matches_blobs(
+       git_patch *p, git_blob *a, git_blob *b,
+       int hunks, int l0, int l1, int ctxt, int adds, int dels)
+{
+       const git_diff_delta *delta;
+       size_t tc, ta, td;
+
+       cl_assert(p != NULL);
+
+       delta = git_patch_get_delta(p);
+       cl_assert(delta != NULL);
+
+       cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status);
+       cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.id));
+       cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size);
+       cl_assert(git_oid_equal(git_blob_id(b), &delta->new_file.id));
+       cl_assert_equal_sz(git_blob_rawsize(b), delta->new_file.size);
+
+       cl_assert_equal_i(hunks, (int)git_patch_num_hunks(p));
+
+       if (hunks > 0)
+               cl_assert_equal_i(l0, git_patch_num_lines_in_hunk(p, 0));
+       if (hunks > 1)
+               cl_assert_equal_i(l1, git_patch_num_lines_in_hunk(p, 1));
+
+       cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p));
+       cl_assert_equal_i(ctxt, (int)tc);
+       cl_assert_equal_i(adds, (int)ta);
+       cl_assert_equal_i(dels, (int)td);
+}
+
 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_patch *p;
-       const git_diff_delta *delta;
-       size_t tc, ta, td;
 
        /* tests/resources/attr/root_test1 */
        cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8));
@@ -161,92 +177,22 @@ void test_diff_blob__can_compare_text_blobs_with_patch(void)
 
        /* diff on tests/resources/attr/root_test1 */
        cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, &opts));
-
-       cl_assert(p != NULL);
-
-       delta = git_patch_get_delta(p);
-       cl_assert(delta != NULL);
-       cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status);
-       cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size);
-       cl_assert(git_oid_equal(git_blob_id(b), &delta->new_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(b), delta->new_file.size);
-
-       cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
-       cl_assert_equal_i(6, git_patch_num_lines_in_hunk(p, 0));
-
-       cl_git_pass(git_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);
-
+       assert_patch_matches_blobs(p, a, b, 1, 6, 0, 1, 5, 0);
        git_patch_free(p);
 
        /* diff on tests/resources/attr/root_test2 */
        cl_git_pass(git_patch_from_blobs(&p, b, NULL, c, NULL, &opts));
-
-       cl_assert(p != NULL);
-
-       delta = git_patch_get_delta(p);
-       cl_assert(delta != NULL);
-       cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status);
-       cl_assert(git_oid_equal(git_blob_id(b), &delta->old_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(b), delta->old_file.size);
-       cl_assert(git_oid_equal(git_blob_id(c), &delta->new_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(c), delta->new_file.size);
-
-       cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
-       cl_assert_equal_i(15, git_patch_num_lines_in_hunk(p, 0));
-
-       cl_git_pass(git_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);
-
+       assert_patch_matches_blobs(p, b, c, 1, 15, 0, 3, 9, 3);
        git_patch_free(p);
 
        /* diff on tests/resources/attr/root_test3 */
        cl_git_pass(git_patch_from_blobs(&p, a, NULL, c, NULL, &opts));
-
-       cl_assert(p != NULL);
-
-       delta = git_patch_get_delta(p);
-       cl_assert(delta != NULL);
-       cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status);
-       cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size);
-       cl_assert(git_oid_equal(git_blob_id(c), &delta->new_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(c), delta->new_file.size);
-
-       cl_git_pass(git_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);
-
+       assert_patch_matches_blobs(p, a, c, 1, 13, 0, 0, 12, 1);
        git_patch_free(p);
 
        /* one more */
        cl_git_pass(git_patch_from_blobs(&p, c, NULL, d, NULL, &opts));
-
-       cl_assert(p != NULL);
-
-       delta = git_patch_get_delta(p);
-       cl_assert(delta != NULL);
-       cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status);
-       cl_assert(git_oid_equal(git_blob_id(c), &delta->old_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(c), delta->old_file.size);
-       cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.id));
-       cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size);
-
-       cl_assert_equal_i(2, (int)git_patch_num_hunks(p));
-       cl_assert_equal_i(5, git_patch_num_lines_in_hunk(p, 0));
-       cl_assert_equal_i(9, git_patch_num_lines_in_hunk(p, 1));
-
-       cl_git_pass(git_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);
-
+       assert_patch_matches_blobs(p, c, d, 2, 5, 9, 4, 6, 4);
        git_patch_free(p);
 
        git_blob_free(a);
@@ -656,14 +602,7 @@ void test_diff_blob__can_compare_blob_to_buffer(void)
 
        /* diff from blob a to content of b */
        quick_diff_blob_to_str(a, NULL, b_content, 0, NULL);
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-       cl_assert_equal_i(1, expected.hunks);
-       cl_assert_equal_i(6, expected.lines);
-       cl_assert_equal_i(1, expected.line_ctxt);
-       cl_assert_equal_i(5, expected.line_adds);
-       cl_assert_equal_i(0, expected.line_dels);
+       assert_one_modified(1, 6, 1, 5, 0, &expected);
 
        /* diff from blob a to content of a */
        opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
@@ -910,14 +849,7 @@ void test_diff_blob__using_path_and_attributes(void)
        changed = "Hello from the root\nMore lines\nAnd more\nGo here\n";
 
        quick_diff_blob_to_str(nonbin, NULL, changed, 0, NULL);
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-       cl_assert_equal_i(1, expected.hunks);
-       cl_assert_equal_i(3, expected.lines);
-       cl_assert_equal_i(0, expected.line_ctxt);
-       cl_assert_equal_i(3, expected.line_adds);
-       cl_assert_equal_i(0, expected.line_dels);
+       assert_one_modified(1, 3, 0, 3, 0, &expected);
 
        quick_diff_blob_to_str(nonbin, "foo/bar.binary", changed, 0, NULL);
        cl_assert_equal_i(1, expected.files);
@@ -925,29 +857,12 @@ void test_diff_blob__using_path_and_attributes(void)
        cl_assert_equal_i(1, expected.files_binary);
        cl_assert_equal_i(0, expected.hunks);
        cl_assert_equal_i(0, expected.lines);
-       cl_assert_equal_i(0, expected.line_ctxt);
-       cl_assert_equal_i(0, expected.line_adds);
-       cl_assert_equal_i(0, expected.line_dels);
 
        quick_diff_blob_to_str(nonbin, "foo/bar.textary", changed, 0, NULL);
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-       cl_assert_equal_i(1, expected.hunks);
-       cl_assert_equal_i(3, expected.lines);
-       cl_assert_equal_i(0, expected.line_ctxt);
-       cl_assert_equal_i(3, expected.line_adds);
-       cl_assert_equal_i(0, expected.line_dels);
+       assert_one_modified(1, 3, 0, 3, 0, &expected);
 
        quick_diff_blob_to_str(nonbin, "foo/bar.alphary", changed, 0, NULL);
-       cl_assert_equal_i(1, expected.files);
-       cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
-       cl_assert_equal_i(0, expected.files_binary);
-       cl_assert_equal_i(1, expected.hunks);
-       cl_assert_equal_i(3, expected.lines);
-       cl_assert_equal_i(0, expected.line_ctxt);
-       cl_assert_equal_i(3, expected.line_adds);
-       cl_assert_equal_i(0, expected.line_dels);
+       assert_one_modified(1, 3, 0, 3, 0, &expected);
 
        cl_git_pass(git_patch_from_blob_and_buffer(
                &p, nonbin, "zzz.normal", changed, strlen(changed), NULL, &opts));
@@ -1066,3 +981,28 @@ void test_diff_blob__using_path_and_attributes(void)
        git_blob_free(nonbin);
        git_blob_free(bin);
 }
+
+void test_diff_blob__can_compare_buffer_to_buffer(void)
+{
+       const char *a = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n";
+       const char *b = "a\nB\nc\nd\nE\nF\nh\nj\nk\n";
+
+       opts.interhunk_lines = 0;
+       opts.context_lines = 0;
+
+       memset(&expected, 0, sizeof(expected));
+
+       cl_git_pass(git_diff_buffers(
+               a, strlen(a), NULL, b, strlen(b), NULL,
+               &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+       assert_one_modified(4, 9, 0, 4, 5, &expected);
+
+       opts.flags ^= GIT_DIFF_REVERSE;
+
+       memset(&expected, 0, sizeof(expected));
+
+       cl_git_pass(git_diff_buffers(
+               a, strlen(a), NULL, b, strlen(b), NULL,
+               &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+       assert_one_modified(4, 9, 0, 5, 4, &expected);
+}