Commit 7263057269ee7131093046205abfcf5938a59ebf

Jason Haslam 2017-03-30T22:40:47

patch: add support for partial patch application Add hunk callback parameter to git_apply__patch to allow hunks to be skipped.

diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c
index 3b15602..8a6d8fa 100644
--- a/tests/apply/fromdiff.c
+++ b/tests/apply/fromdiff.c
@@ -150,6 +150,52 @@ void test_apply_fromdiff__prepend_nocontext(void)
 		PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts));
 }
 
+void test_apply_fromdiff__prepend_and_change(void)
+{
+	cl_git_pass(apply_buf(
+		FILE_ORIGINAL, "file.txt",
+		FILE_PREPEND_AND_CHANGE, "file.txt",
+		PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE, NULL));
+}
+
+void test_apply_fromdiff__prepend_and_change_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_AND_CHANGE, "file.txt",
+		PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT, &diff_opts));
+}
+
+void test_apply_fromdiff__delete_and_change(void)
+{
+	cl_git_pass(apply_buf(
+		FILE_ORIGINAL, "file.txt",
+		FILE_DELETE_AND_CHANGE, "file.txt",
+		PATCH_ORIGINAL_TO_DELETE_AND_CHANGE, NULL));
+}
+
+void test_apply_fromdiff__delete_and_change_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_DELETE_AND_CHANGE, "file.txt",
+		PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT, &diff_opts));
+}
+
+void test_apply_fromdiff__delete_firstline(void)
+{
+	cl_git_pass(apply_buf(
+		FILE_ORIGINAL, "file.txt",
+		FILE_DELETE_FIRSTLINE, "file.txt",
+		PATCH_ORIGINAL_TO_DELETE_FIRSTLINE, NULL));
+}
+
 void test_apply_fromdiff__append(void)
 {
 	cl_git_pass(apply_buf(
diff --git a/tests/apply/partial.c b/tests/apply/partial.c
new file mode 100644
index 0000000..bdbf35a
--- /dev/null
+++ b/tests/apply/partial.c
@@ -0,0 +1,161 @@
+#include "clar_libgit2.h"
+#include "git2/sys/repository.h"
+
+#include "apply.h"
+#include "repository.h"
+#include "buf_text.h"
+
+#include "../patch/patch_common.h"
+
+static git_repository *repo = NULL;
+
+void test_apply_partial__initialize(void)
+{
+	repo = cl_git_sandbox_init("renames");
+}
+
+void test_apply_partial__cleanup(void)
+{
+	cl_git_sandbox_cleanup();
+}
+
+static int skip_addition(
+	const git_diff_hunk *hunk,
+	void *payload)
+{
+	GIT_UNUSED(payload);
+
+	return (hunk->new_lines > hunk->old_lines) ? 1 : 0;
+}
+
+static int skip_deletion(
+	const git_diff_hunk *hunk,
+	void *payload)
+{
+	GIT_UNUSED(payload);
+
+	return (hunk->new_lines < hunk->old_lines) ? 1 : 0;
+}
+
+static int skip_change(
+	const git_diff_hunk *hunk,
+	void *payload)
+{
+	GIT_UNUSED(payload);
+
+	return (hunk->new_lines == hunk->old_lines) ? 1 : 0;
+}
+
+static int apply_buf(
+	const char *old,
+	const char *oldname,
+	const char *new,
+	const char *newname,
+	const char *expected,
+	const git_diff_options *diff_opts,
+	git_apply_hunk_cb hunk_cb,
+	void *payload)
+{
+	git_patch *patch;
+	git_buf result = GIT_BUF_INIT;
+	git_buf patchbuf = GIT_BUF_INIT;
+	git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
+	char *filename;
+	unsigned int mode;
+	int error;
+	size_t oldsize = strlen(old);
+	size_t newsize = strlen(new);
+
+	opts.hunk_cb = hunk_cb;
+	opts.payload = payload;
+
+	cl_git_pass(git_patch_from_buffers(&patch, old, oldsize, oldname, new, newsize, newname, diff_opts));
+	if ((error = git_apply__patch(&result, &filename, &mode, old, oldsize, patch, &opts)) == 0) {
+		cl_assert_equal_s(expected, result.ptr);
+		cl_assert_equal_s(newname, 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_partial__prepend_and_change_skip_addition(void)
+{
+	cl_git_pass(apply_buf(
+		FILE_ORIGINAL, "file.txt",
+		FILE_PREPEND_AND_CHANGE, "file.txt",
+		FILE_ORIGINAL, NULL, skip_addition, NULL));
+}
+
+void test_apply_partial__prepend_and_change_nocontext_skip_addition(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_AND_CHANGE, "file.txt",
+		FILE_CHANGE_MIDDLE, &diff_opts, skip_addition, NULL));
+}
+
+void test_apply_partial__prepend_and_change_skip_change(void)
+{
+	cl_git_pass(apply_buf(
+		FILE_ORIGINAL, "file.txt",
+		FILE_PREPEND_AND_CHANGE, "file.txt",
+		FILE_PREPEND_AND_CHANGE, NULL, skip_change, NULL));
+}
+
+void test_apply_partial__prepend_and_change_nocontext_skip_change(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_AND_CHANGE, "file.txt",
+		FILE_PREPEND, &diff_opts, skip_change, NULL));
+}
+
+void test_apply_partial__delete_and_change_skip_deletion(void)
+{
+	cl_git_pass(apply_buf(
+		FILE_ORIGINAL, "file.txt",
+		FILE_DELETE_AND_CHANGE, "file.txt",
+		FILE_ORIGINAL, NULL, skip_deletion, NULL));
+}
+
+void test_apply_partial__delete_and_change_nocontext_skip_deletion(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_DELETE_AND_CHANGE, "file.txt",
+		FILE_CHANGE_MIDDLE, &diff_opts, skip_deletion, NULL));
+}
+
+void test_apply_partial__delete_and_change_skip_change(void)
+{
+	cl_git_pass(apply_buf(
+		FILE_ORIGINAL, "file.txt",
+		FILE_DELETE_AND_CHANGE, "file.txt",
+		FILE_DELETE_AND_CHANGE, NULL, skip_change, NULL));
+}
+
+void test_apply_partial__delete_and_change_nocontext_skip_change(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_DELETE_AND_CHANGE, "file.txt",
+		FILE_DELETE_FIRSTLINE, &diff_opts, skip_change, NULL));
+}
diff --git a/tests/patch/patch_common.h b/tests/patch/patch_common.h
index e838e60..3f2668d 100644
--- a/tests/patch/patch_common.h
+++ b/tests/patch/patch_common.h
@@ -220,6 +220,112 @@
 	"@@ -0,0 +1 @@\n" \
 	"+insert at front\n"
 
+/* An insertion at the beginning of the file and change in the middle */
+
+#define FILE_PREPEND_AND_CHANGE \
+	"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_AND_CHANGE \
+	"diff --git a/file.txt b/file.txt\n" \
+	"index 9432026..f73c8bb 100644\n" \
+	"--- a/file.txt\n" \
+	"+++ b/file.txt\n" \
+	"@@ -1,9 +1,10 @@\n" \
+	"+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" \
+	"+(THIS line is changed!)\n" \
+	" and this\n" \
+	" is additional context\n" \
+	" below it!\n"
+
+#define PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT \
+	"diff --git a/file.txt b/file.txt\n" \
+	"index 9432026..f73c8bb 100644\n" \
+	"--- a/file.txt\n" \
+	"+++ b/file.txt\n" \
+	"@@ -0,0 +1 @@\n" \
+	"+insert at front\n" \
+	"@@ -6 +7 @@ yes it is!\n" \
+	"-(this line is changed)\n" \
+	"+(THIS line is changed!)\n"
+
+/* A deletion at the beginning of the file and a change in the middle */
+
+#define FILE_DELETE_AND_CHANGE \
+	"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_DELETE_AND_CHANGE \
+	"diff --git a/file.txt b/file.txt\n" \
+	"index 9432026..1e2dfa6 100644\n" \
+	"--- a/file.txt\n" \
+	"+++ b/file.txt\n" \
+	"@@ -1,9 +1,8 @@\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" \
+	"+(THIS line is changed!)\n" \
+	" and this\n" \
+	" is additional context\n" \
+	" below it!\n"
+
+#define PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT \
+	"diff --git a/file.txt b/file.txt\n" \
+	"index 9432026..1e2dfa6 100644\n" \
+	"--- a/file.txt\n" \
+	"+++ b/file.txt\n" \
+	"@@ -1 +0,0 @@\n" \
+	"-hey!\n" \
+	"@@ -6 +5 @@ yes it is!\n" \
+	"-(this line is changed)\n" \
+	"+(THIS line is changed!)\n"
+
+/* A deletion at the beginning of the file */
+
+#define FILE_DELETE_FIRSTLINE \
+	"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_DELETE_FIRSTLINE \
+	"diff --git a/file.txt b/file.txt\n" \
+	"index 9432026..f31fa13 100644\n" \
+	"--- a/file.txt\n" \
+	"+++ b/file.txt\n" \
+	"@@ -1,4 +1,3 @@\n" \
+	"-hey!\n" \
+	" this is some context!\n" \
+	" around some lines\n" \
+	" that will change\n"
+
 /* An insertion at the end of the file (and the resultant patch) */
 
 #define FILE_APPEND \