]> git.proxmox.com Git - libgit2.git/commitdiff
Introduce git_apply_patch
authorEdward Thomson <ethomson@microsoft.com>
Wed, 2 Apr 2014 06:58:59 +0000 (23:58 -0700)
committerEdward Thomson <ethomson@github.com>
Thu, 26 May 2016 16:36:11 +0000 (11:36 -0500)
The beginnings of patch application from an existing (diff-created)
git_patch object: applies the hunks of a git_patch to a buffer.

include/git2/errors.h
src/apply.c [new file with mode: 0644]
src/apply.h [new file with mode: 0644]
src/array.h
src/vector.c
src/vector.h
tests/apply/apply_common.h [new file with mode: 0644]
tests/apply/fromdiff.c [new file with mode: 0644]

index 3ecea34bf5876c80e2ea5eac0ca167052bf2b3fb..e959ffd8added343a2d0da12a25be56bfa57b7a1 100644 (file)
@@ -98,7 +98,8 @@ typedef enum {
        GITERR_CHERRYPICK,
        GITERR_DESCRIBE,
        GITERR_REBASE,
-       GITERR_FILESYSTEM
+       GITERR_FILESYSTEM,
+       GITERR_PATCH,
 } git_error_t;
 
 /**
diff --git a/src/apply.c b/src/apply.c
new file mode 100644 (file)
index 0000000..e75fa5b
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "git2/patch.h"
+#include "git2/filter.h"
+#include "array.h"
+#include "diff_patch.h"
+#include "fileops.h"
+#include "apply.h"
+
+#define apply_err(...) \
+       ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
+
+typedef struct {
+       /* The lines that we allocate ourself are allocated out of the pool.
+        * (Lines may have been allocated out of the diff.)
+        */
+       git_pool pool;
+       git_vector lines;
+} patch_image;
+
+static void patch_line_init(
+       git_diff_line *out,
+       const char *in,
+       size_t in_len,
+       size_t in_offset)
+{
+       out->content = in;
+       out->content_len = in_len;
+       out->content_offset = in_offset;
+}
+
+static unsigned int patch_image_init(patch_image *out)
+{
+       memset(out, 0x0, sizeof(patch_image));
+       return 0;
+}
+
+static int patch_image_init_fromstr(
+       patch_image *out, const char *in, size_t in_len)
+{
+       git_diff_line *line;
+       const char *start, *end;
+
+       memset(out, 0x0, sizeof(patch_image));
+
+       git_pool_init(&out->pool, sizeof(git_diff_line));
+
+       for (start = in; start < in + in_len; start = end) {
+               end = memchr(start, '\n', in_len);
+
+               if (end < in + in_len)
+                       end++;
+
+               line = git_pool_mallocz(&out->pool, 1);
+               GITERR_CHECK_ALLOC(line);
+
+               if (git_vector_insert(&out->lines, line) < 0)
+                       return -1;
+
+               patch_line_init(line, start, (end - start), (start - in));
+       }
+
+       return 0;
+}
+
+static void patch_image_free(patch_image *image)
+{
+       if (image == NULL)
+               return;
+
+       git_pool_clear(&image->pool);
+       git_vector_free(&image->lines);
+}
+
+static bool match_hunk(
+       patch_image *image,
+       patch_image *preimage,
+       size_t linenum)
+{
+       bool match = 0;
+       size_t i;
+
+       /* Ensure this hunk is within the image boundaries. */
+       if (git_vector_length(&preimage->lines) + linenum >
+               git_vector_length(&image->lines))
+               return 0;
+
+       match = 1;
+
+       /* Check exact match. */
+       for (i = 0; i < git_vector_length(&preimage->lines); i++) {
+               git_diff_line *preimage_line = git_vector_get(&preimage->lines, i);
+               git_diff_line *image_line = git_vector_get(&image->lines, linenum + i);
+
+               if (preimage_line->content_len != preimage_line->content_len ||
+                       memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) {
+                       match = 0;
+                       break;
+               }
+       }
+
+       return match;
+}
+
+static bool find_hunk_linenum(
+       size_t *out,
+       patch_image *image,
+       patch_image *preimage,
+       size_t linenum)
+{
+       size_t max = git_vector_length(&image->lines);
+       bool match;
+
+       if (linenum > max)
+               linenum = max;
+
+       match = match_hunk(image, preimage, linenum);
+
+       *out = linenum;
+       return match;
+}
+
+static int update_hunk(
+       patch_image *image,
+       unsigned int linenum,
+       patch_image *preimage,
+       patch_image *postimage)
+{
+       size_t postlen = git_vector_length(&postimage->lines);
+       size_t prelen = git_vector_length(&preimage->lines);
+       size_t i;
+       int error = 0;
+
+       if (postlen > prelen)
+               error = git_vector_grow_at(
+                       &image->lines, linenum, (postlen - prelen));
+       else if (prelen > postlen)
+               error = git_vector_shrink_at(
+                       &image->lines, linenum, (prelen - postlen));
+
+       if (error) {
+               giterr_set_oom();
+               return -1;
+       }
+
+       for (i = 0; i < git_vector_length(&postimage->lines); i++) {
+               image->lines.contents[linenum + i] =
+                       git_vector_get(&postimage->lines, i);
+       }
+
+       return 0;
+}
+
+static int apply_hunk(
+       patch_image *image,
+       git_patch *patch,
+       diff_patch_hunk *hunk)
+{
+       patch_image preimage, postimage;
+       size_t line_num, i;
+       int error = 0;
+
+       if ((error = patch_image_init(&preimage)) < 0 ||
+               (error = patch_image_init(&postimage)) < 0)
+               goto done;
+
+       for (i = 0; i < hunk->line_count; i++) {
+               size_t linenum = hunk->line_start + i;
+               git_diff_line *line = git_array_get(patch->lines, linenum);
+
+               if (!line) {
+                       error = apply_err("Preimage does not contain line %d", linenum);
+                       goto done;
+               }
+
+               if (line->origin == GIT_DIFF_LINE_CONTEXT ||
+                       line->origin == GIT_DIFF_LINE_DELETION) {
+                       if ((error = git_vector_insert(&preimage.lines, line)) < 0)
+                               goto done;
+               }
+
+               if (line->origin == GIT_DIFF_LINE_CONTEXT ||
+                       line->origin == GIT_DIFF_LINE_ADDITION) {
+                       if ((error = git_vector_insert(&postimage.lines, line)) < 0)
+                               goto done;
+               }
+       }
+
+       line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0;
+
+       if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) {
+               error = apply_err("Hunk at line %d did not apply",
+                       hunk->hunk.new_start);
+               goto done;
+       }
+
+       error = update_hunk(image, line_num, &preimage, &postimage);
+
+done:
+       patch_image_free(&preimage);
+       patch_image_free(&postimage);
+
+       return error;
+}
+
+static int apply_hunks(
+       git_buf *out,
+       const char *source,
+       size_t source_len,
+       git_patch *patch)
+{
+       diff_patch_hunk *hunk;
+       git_diff_line *line;
+       patch_image image;
+       size_t i;
+       int error = 0;
+
+       if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0)
+               goto done;
+
+       git_array_foreach(patch->hunks, i, hunk) {
+               if ((error = apply_hunk(&image, patch, hunk)) < 0)
+                       goto done;
+       }
+
+       git_vector_foreach(&image.lines, i, line)
+               git_buf_put(out, line->content, line->content_len);
+
+done:
+       patch_image_free(&image);
+
+       return error;
+}
+
+int git_apply__patch(
+       git_buf *contents_out,
+       char **filename_out,
+       unsigned int *mode_out,
+       const char *source,
+       size_t source_len,
+       git_patch *patch)
+{
+       char *filename = NULL;
+       unsigned int mode = 0;
+       int error = 0;
+
+       assert(contents_out && filename_out && mode_out && (source || !source_len) && patch);
+
+       *filename_out = NULL;
+       *mode_out = 0;
+
+       if (patch->delta->status != GIT_DELTA_DELETED) {
+               filename = git__strdup(patch->nfile.file->path);
+               mode = patch->nfile.file->mode ?
+                       patch->nfile.file->mode : GIT_FILEMODE_BLOB;
+       }
+
+       if ((error = apply_hunks(contents_out, source, source_len, patch)) < 0)
+               goto done;
+
+       if (patch->delta->status == GIT_DELTA_DELETED &&
+               git_buf_len(contents_out) > 0) {
+               error = apply_err("removal patch leaves file contents");
+               goto done;
+       }
+
+       *filename_out = filename;
+       *mode_out = mode;
+
+done:
+       if (error < 0)
+               git__free(filename);
+
+       return error;
+}
diff --git a/src/apply.h b/src/apply.h
new file mode 100644 (file)
index 0000000..96e0f55
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_apply_h__
+#define INCLUDE_apply_h__
+
+#include "git2/patch.h"
+#include "buffer.h"
+
+extern int git_apply__patch(
+       git_buf *out,
+       char **filename,
+       unsigned int *mode,
+       const char *source,
+       size_t source_len,
+       git_patch *patch);
+
+#endif
index 78d321e8237f636047c90e2703a041bd3f380b13..1d8a01c9626cb6081a28db0cecac522a3ca4b139 100644 (file)
@@ -85,7 +85,6 @@ on_oom:
 #define git_array_foreach(a, i, element) \
        for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
 
-
 GIT_INLINE(int) git_array__search(
        size_t *out,
        void *array_ptr,
index a81d463efa9fee555865ea01d8d59ceb63349612..3684676920adef17a200c424b4b6bbc49cef48e4 100644 (file)
@@ -330,6 +330,47 @@ int git_vector_resize_to(git_vector *v, size_t new_length)
        return 0;
 }
 
+int git_vector_grow_at(git_vector *v, size_t idx, size_t grow_len)
+{
+       size_t new_length = v->length + grow_len;
+       size_t new_idx = idx + grow_len;
+
+       assert(grow_len > 0);
+       assert (idx <= v->length);
+
+       if (new_length < v->length ||
+               (new_length > v->_alloc_size && resize_vector(v, new_length) < 0))
+               return -1;
+
+       memmove(&v->contents[new_idx], &v->contents[idx],
+               sizeof(void *) * (v->length - idx));
+       memset(&v->contents[idx], 0, sizeof(void *) * grow_len);
+
+       v->length = new_length;
+       return 0;
+}
+
+int git_vector_shrink_at(git_vector *v, size_t idx, size_t shrink_len)
+{
+       size_t new_length = v->length - shrink_len;
+       size_t end_idx = idx + shrink_len;
+
+       assert(shrink_len > 0 && shrink_len <= v->length);
+       assert(idx <= v->length);
+
+       if (new_length > v->length)
+               return -1;
+
+       if (idx > v->length)
+               memmove(&v->contents[idx], &v->contents[end_idx],
+                       sizeof(void *) * (v->length - idx));
+
+       memset(&v->contents[new_length], 0, sizeof(void *) * shrink_len);
+
+       v->length = new_length;
+       return 0;
+}
+
 int git_vector_set(void **old, git_vector *v, size_t position, void *value)
 {
        if (position + 1 > v->length) {
index b7500ded3a7b7baea7369faf7af5189d5d7d7eb4..6399a84842ed82e313391e8fc7ab92c3c5477a4a 100644 (file)
@@ -93,6 +93,9 @@ void git_vector_remove_matching(
        void *payload);
 
 int git_vector_resize_to(git_vector *v, size_t new_length);
+int git_vector_grow_at(git_vector *v, size_t idx, size_t grow_len);
+int git_vector_shrink_at(git_vector *v, size_t idx, size_t shrink_len);
+
 int git_vector_set(void **old, git_vector *v, size_t position, void *value);
 
 /** Check if vector is sorted */
diff --git a/tests/apply/apply_common.h b/tests/apply/apply_common.h
new file mode 100644 (file)
index 0000000..8226cc1
--- /dev/null
@@ -0,0 +1,286 @@
+/* The original file contents */
+
+#define FILE_ORIGINAL \
+       "hey!\n" \
+       "this is some context!\n" \
+       "around some lines\n" \
+       "that will change\n" \
+       "yes it is!\n" \
+       "(this line is changed)\n" \
+       "and this\n" \
+       "is additional context\n" \
+       "below it!\n"
+
+/* A change in the middle of the file (and the resultant patch) */
+
+#define FILE_CHANGE_MIDDLE \
+       "hey!\n" \
+       "this is some context!\n" \
+       "around some lines\n" \
+       "that will change\n" \
+       "yes it is!\n" \
+       "(THIS line is changed!)\n" \
+       "and this\n" \
+       "is additional context\n" \
+       "below it!\n"
+
+#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..cd8fd12 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -3,7 +3,7 @@ this is some context!\n" \
+       " around some lines\n" \
+       " that will change\n" \
+       " yes it is!\n" \
+       "-(this line is changed)\n" \
+       "+(THIS line is changed!)\n" \
+       " and this\n" \
+       " is additional context\n" \
+       " below it!\n"
+
+#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..cd8fd12 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -6 +6 @@ yes it is!\n" \
+       "-(this line is changed)\n" \
+       "+(THIS line is changed!)\n"
+
+/* A change of the first line (and the resultant patch) */
+
+#define FILE_CHANGE_FIRSTLINE \
+       "hey, change in head!\n" \
+       "this is some context!\n" \
+       "around some lines\n" \
+       "that will change\n" \
+       "yes it is!\n" \
+       "(this line is changed)\n" \
+       "and this\n" \
+       "is additional context\n" \
+       "below it!\n"
+
+#define PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..c81df1d 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -1,4 +1,4 @@\n" \
+       "-hey!\n" \
+       "+hey, change in head!\n" \
+       " this is some context!\n" \
+       " around some lines\n" \
+       " that will change\n"
+
+/* A change of the last line (and the resultant patch) */
+
+#define FILE_CHANGE_LASTLINE \
+       "hey!\n" \
+       "this is some context!\n" \
+       "around some lines\n" \
+       "that will change\n" \
+       "yes it is!\n" \
+       "(this line is changed)\n" \
+       "and this\n" \
+       "is additional context\n" \
+       "change to the last line.\n"
+
+#define PATCH_ORIGINAL_TO_CHANGE_LASTLINE \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..f70db1c 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -6,4 +6,4 @@ yes it is!\n" \
+       " (this line is changed)\n" \
+       " and this\n" \
+       " is additional context\n" \
+       "-below it!\n" \
+       "+change to the last line.\n"
+
+/* An insertion at the beginning of the file (and the resultant patch) */
+
+#define FILE_PREPEND \
+       "insert at front\n" \
+       "hey!\n" \
+       "this is some context!\n" \
+       "around some lines\n" \
+       "that will change\n" \
+       "yes it is!\n" \
+       "(this line is changed)\n" \
+       "and this\n" \
+       "is additional context\n" \
+       "below it!\n"
+
+#define PATCH_ORIGINAL_TO_PREPEND \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..0f39b9a 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -1,3 +1,4 @@\n" \
+       "+insert at front\n" \
+       " hey!\n" \
+       " this is some context!\n" \
+       " around some lines\n"
+
+#define PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..0f39b9a 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -0,0 +1 @@\n" \
+       "+insert at front\n"
+
+/* An insertion at the end of the file (and the resultant patch) */
+
+#define FILE_APPEND \
+       "hey!\n" \
+       "this is some context!\n" \
+       "around some lines\n" \
+       "that will change\n" \
+       "yes it is!\n" \
+       "(this line is changed)\n" \
+       "and this\n" \
+       "is additional context\n" \
+       "below it!\n" \
+       "insert at end\n"
+
+#define PATCH_ORIGINAL_TO_APPEND \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..72788bb 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -7,3 +7,4 @@ yes it is!\n" \
+       " and this\n" \
+       " is additional context\n" \
+       " below it!\n" \
+       "+insert at end\n"
+
+#define PATCH_ORIGINAL_TO_APPEND_NOCONTEXT \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..72788bb 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -9,0 +10 @@ below it!\n" \
+       "+insert at end\n"
+
+/* An insertion at the beginning and end of file (and the resultant patch) */
+
+#define FILE_PREPEND_AND_APPEND \
+       "first and\n" \
+       "this is some context!\n" \
+       "around some lines\n" \
+       "that will change\n" \
+       "yes it is!\n" \
+       "(this line is changed)\n" \
+       "and this\n" \
+       "is additional context\n" \
+       "last lines\n"
+
+#define PATCH_ORIGINAL_TO_PREPEND_AND_APPEND \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..f282430 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -1,4 +1,4 @@\n" \
+       "-hey!\n" \
+       "+first and\n" \
+       " this is some context!\n" \
+       " around some lines\n" \
+       " that will change\n" \
+       "@@ -6,4 +6,4 @@ yes it is!\n" \
+       " (this line is changed)\n" \
+       " and this\n" \
+       " is additional context\n" \
+       "-below it!\n" \
+       "+last lines\n"
+
+#define PATCH_ORIGINAL_TO_EMPTY_FILE \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index 9432026..e69de29 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -1,9 +0,0 @@\n" \
+       "-hey!\n" \
+       "-this is some context!\n" \
+       "-around some lines\n" \
+       "-that will change\n" \
+       "-yes it is!\n" \
+       "-(this line is changed)\n" \
+       "-and this\n" \
+       "-is additional context\n" \
+       "-below it!\n"
+
+#define PATCH_EMPTY_FILE_TO_ORIGINAL \
+       "diff --git a/file.txt b/file.txt\n" \
+       "index e69de29..9432026 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/file.txt\n" \
+       "@@ -0,0 +1,9 @@\n" \
+       "+hey!\n" \
+       "+this is some context!\n" \
+       "+around some lines\n" \
+       "+that will change\n" \
+       "+yes it is!\n" \
+       "+(this line is changed)\n" \
+       "+and this\n" \
+       "+is additional context\n" \
+       "+below it!\n"
+
+#define PATCH_ADD_ORIGINAL \
+       "diff --git a/file.txt b/file.txt\n" \
+       "new file mode 100644\n" \
+       "index 0000000..9432026\n" \
+       "--- /dev/null\n" \
+       "+++ b/file.txt\n" \
+       "@@ -0,0 +1,9 @@\n" \
+       "+hey!\n" \
+       "+this is some context!\n" \
+       "+around some lines\n" \
+       "+that will change\n" \
+       "+yes it is!\n" \
+       "+(this line is changed)\n" \
+       "+and this\n" \
+       "+is additional context\n" \
+       "+below it!\n"
+
+#define PATCH_DELETE_ORIGINAL \
+       "diff --git a/file.txt b/file.txt\n" \
+       "deleted file mode 100644\n" \
+       "index 9432026..0000000\n" \
+       "--- a/file.txt\n" \
+       "+++ /dev/null\n" \
+       "@@ -1,9 +0,0 @@\n" \
+       "-hey!\n" \
+       "-this is some context!\n" \
+       "-around some lines\n" \
+       "-that will change\n" \
+       "-yes it is!\n" \
+       "-(this line is changed)\n" \
+       "-and this\n" \
+       "-is additional context\n" \
+       "-below it!\n"
+
+#define PATCH_RENAME_EXACT \
+       "diff --git a/file.txt b/newfile.txt\n" \
+       "similarity index 100%\n" \
+       "rename from file.txt\n" \
+       "rename to newfile.txt\n"
+
+#define PATCH_RENAME_SIMILAR \
+       "diff --git a/file.txt b/newfile.txt\n" \
+       "similarity index 77%\n" \
+       "rename from file.txt\n" \
+       "rename to newfile.txt\n" \
+       "index 9432026..cd8fd12 100644\n" \
+       "--- a/file.txt\n" \
+       "+++ b/newfile.txt\n" \
+       "@@ -3,7 +3,7 @@ this is some context!\n" \
+       " around some lines\n" \
+       " that will change\n" \
+       " yes it is!\n" \
+       "-(this line is changed)\n" \
+       "+(THIS line is changed!)\n" \
+       " and this\n" \
+       " is additional context\n" \
+       " below it!\n"
diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c
new file mode 100644 (file)
index 0000000..64ed9de
--- /dev/null
@@ -0,0 +1,176 @@
+#include "clar_libgit2.h"
+#include "git2/sys/repository.h"
+
+#include "apply.h"
+#include "repository.h"
+#include "buf_text.h"
+
+#include "apply_common.h"
+
+static git_repository *repo = NULL;
+
+void test_apply_fromdiff__initialize(void)
+{
+       repo = cl_git_sandbox_init("renames");
+}
+
+void test_apply_fromdiff__cleanup(void)
+{
+       cl_git_sandbox_cleanup();
+}
+
+static int apply_buf(
+       const char *old,
+       const char *oldname,
+       const char *new,
+       const char *newname,
+       const char *patch_expected,
+       const git_diff_options *diff_opts)
+{
+       git_patch *patch;
+       git_buf result = GIT_BUF_INIT;
+       git_buf patchbuf = GIT_BUF_INIT;
+       char *filename;
+       unsigned int mode;
+       int error;
+
+       cl_git_pass(git_patch_from_buffers(&patch,
+               old, old ? strlen(old) : 0, oldname,
+               new, new ? strlen(new) : 0, newname,
+               diff_opts));
+       cl_git_pass(git_patch_to_buf(&patchbuf, patch));
+
+       cl_assert_equal_s(patch_expected, patchbuf.ptr);
+
+       error = git_apply__patch(&result, &filename, &mode, old, old ? strlen(old) : 0, patch);
+
+       if (error == 0 && new == NULL) {
+               cl_assert_equal_i(0, result.size);
+               cl_assert_equal_p(NULL, filename);
+               cl_assert_equal_i(0, mode);
+       } else {
+               cl_assert_equal_s(new, result.ptr);
+               cl_assert_equal_s("file.txt", filename);
+               cl_assert_equal_i(0100644, mode);
+       }
+
+       git__free(filename);
+       git_buf_free(&result);
+       git_buf_free(&patchbuf);
+       git_patch_free(patch);
+
+       return error;
+}
+
+void test_apply_fromdiff__change_middle(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_CHANGE_MIDDLE, "file.txt",
+               PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL));
+}
+
+void test_apply_fromdiff__change_middle_nocontext(void)
+{
+       git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+       diff_opts.context_lines = 0;
+
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_CHANGE_MIDDLE, "file.txt",
+               PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, &diff_opts));
+}
+
+void test_apply_fromdiff__change_firstline(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_CHANGE_FIRSTLINE, "file.txt",
+               PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL));
+}
+
+void test_apply_fromdiff__lastline(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_CHANGE_LASTLINE, "file.txt",
+               PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL));
+}
+
+void test_apply_fromdiff__prepend(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_PREPEND, "file.txt",
+               PATCH_ORIGINAL_TO_PREPEND, NULL));
+}
+
+void test_apply_fromdiff__prepend_nocontext(void)
+{
+       git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+       diff_opts.context_lines = 0;
+
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_PREPEND, "file.txt",
+               PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts));
+}
+
+void test_apply_fromdiff__append(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_APPEND, "file.txt",
+               PATCH_ORIGINAL_TO_APPEND, NULL));
+}
+
+void test_apply_fromdiff__append_nocontext(void)
+{
+       git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+       diff_opts.context_lines = 0;
+
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_APPEND, "file.txt",
+               PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts));
+}
+
+void test_apply_fromdiff__prepend_and_append(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               FILE_PREPEND_AND_APPEND, "file.txt",
+               PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL));
+}
+
+void test_apply_fromdiff__to_empty_file(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               "", NULL,
+               PATCH_ORIGINAL_TO_EMPTY_FILE, NULL));
+}
+
+void test_apply_fromdiff__from_empty_file(void)
+{
+       cl_git_pass(apply_buf(
+               "", NULL,
+               FILE_ORIGINAL, "file.txt",
+               PATCH_EMPTY_FILE_TO_ORIGINAL, NULL));
+}
+
+void test_apply_fromdiff__add(void)
+{
+       cl_git_pass(apply_buf(
+               NULL, NULL,
+               FILE_ORIGINAL, "file.txt",
+               PATCH_ADD_ORIGINAL, NULL));
+}
+
+void test_apply_fromdiff__delete(void)
+{
+       cl_git_pass(apply_buf(
+               FILE_ORIGINAL, "file.txt",
+               NULL, NULL,
+               PATCH_DELETE_ORIGINAL, NULL));
+}