Commit e7c85120eab6c942d15c0f5ed3a2c8b6ec667617

Russell Belfer 2013-11-01T11:39:37

More tests and fixed for merging reversed diffs There were a lot more cases to deal with to make sure that our merged (i.e. workdir-to-tree-to-index) diffs were matching the output of core Git.

diff --git a/src/diff_tform.c b/src/diff_tform.c
index 0aec754..28a9cc7 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -46,7 +46,6 @@ fail:
 }
 
 static git_diff_delta *diff_delta__merge_like_cgit(
-	uint16_t flags,
 	const git_diff_delta *a,
 	const git_diff_delta *b,
 	git_pool *pool)
@@ -99,15 +98,46 @@ static git_diff_delta *diff_delta__merge_like_cgit(
 	return dup;
 }
 
-int git_diff_merge(
-	git_diff *onto,
-	const git_diff *from)
+static git_diff_delta *diff_delta__merge_like_cgit_reversed(
+	const git_diff_delta *a,
+	const git_diff_delta *b,
+	git_pool *pool)
+{
+	git_diff_delta *dup;
+
+	/* reversed version of above logic */
+
+	if (a->status == GIT_DELTA_UNMODIFIED)
+		return diff_delta__dup(b, pool);
+
+	if ((dup = diff_delta__dup(a, pool)) == NULL)
+		return NULL;
+
+	if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED)
+		return dup;
+
+	if (dup->status == GIT_DELTA_DELETED) {
+		if (b->status == GIT_DELTA_ADDED)
+			dup->status = GIT_DELTA_UNMODIFIED;
+	} else {
+		dup->status = b->status;
+	}
+
+	git_oid_cpy(&dup->old_file.oid, &b->old_file.oid);
+	dup->old_file.mode  = b->old_file.mode;
+	dup->old_file.size  = b->old_file.size;
+	dup->old_file.flags = b->old_file.flags;
+
+	return dup;
+}
+
+int git_diff_merge(git_diff *onto, const git_diff *from)
 {
 	int error = 0;
 	git_pool onto_pool;
 	git_vector onto_new;
 	git_diff_delta *delta;
-	bool ignore_case = false;
+	bool ignore_case, reversed;
 	unsigned int i, j;
 
 	assert(onto && from);
@@ -115,11 +145,11 @@ int git_diff_merge(
 	if (!from->deltas.length)
 		return 0;
 
-	if ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) !=
-		(from->opts.flags & GIT_DIFF_IGNORE_CASE) ||
-		(onto->opts.flags & GIT_DIFF_REVERSE) !=
-		(from->opts.flags & GIT_DIFF_REVERSE))
-	{
+	ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0);
+	reversed    = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0);
+
+	if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) ||
+		reversed    != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) {
 		giterr_set(GITERR_INVALID,
 			"Attempt to merge diffs created with conflicting options");
 		return -1;
@@ -130,8 +160,6 @@ int git_diff_merge(
 		git_pool_init(&onto_pool, 1, 0) < 0)
 		return -1;
 
-	ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0);
-
 	for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
 		git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
 		const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
@@ -145,8 +173,9 @@ int git_diff_merge(
 			delta = diff_delta__dup(f, &onto_pool);
 			j++;
 		} else {
-			delta = diff_delta__merge_like_cgit(
-				onto->opts.flags, o, f, &onto_pool);
+			delta = reversed ?
+				diff_delta__merge_like_cgit_reversed(o, f, &onto_pool) :
+				diff_delta__merge_like_cgit(o, f, &onto_pool);
 			i++;
 			j++;
 		}
diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c
index 8611be8..fba64ef 100644
--- a/tests-clar/diff/workdir.c
+++ b/tests-clar/diff/workdir.c
@@ -196,6 +196,38 @@ void test_diff_workdir__to_tree(void)
 
 	git_diff_free(diff);
 
+	/* Let's try that once more with a reversed diff */
+
+	opts.flags |= GIT_DIFF_REVERSE;
+
+	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts));
+	cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
+	cl_git_pass(git_diff_merge(diff, diff2));
+	git_diff_free(diff2);
+
+	memset(&exp, 0, sizeof(exp));
+
+	cl_git_pass(git_diff_foreach(
+		diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+	cl_assert_equal_i(16, exp.files);
+	cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]);
+	cl_assert_equal_i(4, exp.file_status[GIT_DELTA_ADDED]);
+	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
+	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+	cl_assert_equal_i(12, exp.hunks);
+
+	cl_assert_equal_i(19, exp.lines);
+	cl_assert_equal_i(3, exp.line_ctxt);
+	cl_assert_equal_i(12, exp.line_dels);
+	cl_assert_equal_i(4, exp.line_adds);
+
+	git_diff_free(diff);
+
+	/* all done now */
+
 	git_tree_free(a);
 	git_tree_free(b);
 }