From 16c23e7d2e11cfadbcf4d8c007d7fefcef5fdd7f Mon Sep 17 00:00:00 2001 From: Edmundo Carmona Antoranz Date: Fri, 13 Jun 2025 20:28:34 +0200 Subject: [PATCH 1/3] merge_file.c - allow ours or theirs to be NULL in git_merge_file_from_index Improve git_merge_file_from_index by adding suport to missing children. --- src/libgit2/merge_file.c | 62 ++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/src/libgit2/merge_file.c b/src/libgit2/merge_file.c index ffe83cf2a3b..573c88adaa9 100644 --- a/src/libgit2/merge_file.c +++ b/src/libgit2/merge_file.c @@ -84,8 +84,8 @@ static int merge_file__xdiff( memset(&xmparam, 0x0, sizeof(xmparam_t)); - if (ours->size > LONG_MAX || - theirs->size > LONG_MAX || + if ((ours && ours->size > LONG_MAX) || + (theirs && theirs->size > LONG_MAX) || (ancestor && ancestor->size > LONG_MAX)) { git_error_set(GIT_ERROR_MERGE, "failed to merge files"); error = -1; @@ -99,15 +99,19 @@ static int merge_file__xdiff( ancestor_mmfile.size = (long)ancestor->size; } - xmparam.file1 = (options.our_label) ? - options.our_label : ours->path; - our_mmfile.ptr = (char *)ours->ptr; - our_mmfile.size = (long)ours->size; + if (ours) { + xmparam.file1 = (options.our_label) ? + options.our_label : ours->path; + our_mmfile.ptr = (char *)ours->ptr; + our_mmfile.size = (long)ours->size; + } - xmparam.file2 = (options.their_label) ? - options.their_label : theirs->path; - their_mmfile.ptr = (char *)theirs->ptr; - their_mmfile.size = (long)theirs->size; + if (theirs) { + xmparam.file2 = (options.their_label) ? + options.their_label : theirs->path; + their_mmfile.ptr = (char *)theirs->ptr; + their_mmfile.size = (long)theirs->size; + } if (options.favor == GIT_MERGE_FILE_FAVOR_OURS) xmparam.favor = XDL_MERGE_FAVOR_OURS; @@ -148,8 +152,8 @@ static int merge_file__xdiff( path = git_merge_file__best_path( ancestor ? ancestor->path : NULL, - ours->path, - theirs->path); + ours ? ours->path : NULL, + theirs ? theirs->path : NULL); if (path != NULL && (out->path = git__strdup(path)) == NULL) { error = -1; @@ -161,8 +165,8 @@ static int merge_file__xdiff( out->len = mmbuffer.size; out->mode = git_merge_file__best_mode( ancestor ? ancestor->mode : 0, - ours->mode, - theirs->mode); + ours ? ours->mode : 0, + theirs ? theirs->mode : 0); done: if (error < 0) @@ -276,15 +280,17 @@ int git_merge_file_from_index( const git_merge_file_options *options) { git_merge_file_input *ancestor_ptr = NULL, - ancestor_input = {0}, our_input = {0}, their_input = {0}; - git_odb *odb = NULL; + ancestor_input = {0}; + git_merge_file_input *our_ptr = NULL, + our_input = {0}; + git_merge_file_input *their_ptr = NULL, + their_input = {0}; + git_odb *odb; git_odb_object *odb_object[3] = { 0 }; int error = 0; GIT_ASSERT_ARG(out); GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ours); - GIT_ASSERT_ARG(theirs); memset(out, 0x0, sizeof(git_merge_file_result)); @@ -299,12 +305,24 @@ int git_merge_file_from_index( ancestor_ptr = &ancestor_input; } - if ((error = merge_file_input_from_index(&our_input, &odb_object[1], odb, ours)) < 0 || - (error = merge_file_input_from_index(&their_input, &odb_object[2], odb, theirs)) < 0) - goto done; + if (ours) { + if ((error = merge_file_input_from_index( + &our_input, &odb_object[1], odb, ours)) < 0) + goto done; + + our_ptr = &our_input; + } + + if (theirs) { + if ((error = merge_file_input_from_index( + &their_input, &odb_object[2], odb, theirs)) < 0) + goto done; + + their_ptr = &their_input; + } error = merge_file__from_inputs(out, - ancestor_ptr, &our_input, &their_input, options); + ancestor_ptr, our_ptr, their_ptr, options); done: git_odb_object_free(odb_object[0]); From ba62ad349799812eaeaa73a3415bd6186b0709be Mon Sep 17 00:00:00 2001 From: Edmundo Carmona Antoranz Date: Sat, 14 Jun 2025 00:02:03 +0200 Subject: [PATCH 2/3] merge.h - improve handling when there is no ancestor in merge_file__best_path In cases when a path is missing from the ancestor and only of the two children is defined (IOW, the object is added), then the path in the child that is defined should be returned. However, merge_file__best_path is only returning a path in cases when there is no ancestor path only if both children are defined and they match each other. Improve merge_file__best_path by handling the case when it is present in only one of the children paths returning this value. --- src/libgit2/merge.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libgit2/merge.h b/src/libgit2/merge.h index 23932905e80..fca9c20e9b4 100644 --- a/src/libgit2/merge.h +++ b/src/libgit2/merge.h @@ -165,6 +165,10 @@ GIT_INLINE(const char *) git_merge_file__best_path( if (!ancestor) { if (ours && theirs && strcmp(ours, theirs) == 0) return ours; + if (ours && !theirs) + return ours; + if (theirs && !ours) + return theirs; return NULL; } From 0370375c0631e679774c17d7d2b1792d94a08871 Mon Sep 17 00:00:00 2001 From: Edmundo Carmona Antoranz Date: Sat, 14 Jun 2025 19:45:33 +0200 Subject: [PATCH 3/3] git_merge_file_from_index: add unit test for deleted/added file --- tests/libgit2/merge/files.c | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/libgit2/merge/files.c b/tests/libgit2/merge/files.c index 0f2dfbdf428..0a393b870f9 100644 --- a/tests/libgit2/merge/files.c +++ b/tests/libgit2/merge/files.c @@ -175,6 +175,52 @@ void test_merge_files__automerge_from_index(void) git_merge_file_result_free(&result); } +void test_merge_files__automerge_from_index_delete_file(void) +{ + git_merge_file_result result = {0}; + git_index_entry ancestor, theirs, *ours = NULL; + + git_oid_from_string(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46", GIT_OID_SHA1); + ancestor.path = "automergeable.txt"; + ancestor.mode = GIT_FILEMODE_BLOB; + + git_oid_from_string(&theirs.id, "d427e0b2e138501a3d15cc376077a3631e15bd46", GIT_OID_SHA1); + theirs.path = "automergeable.txt"; + theirs.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_merge_file_from_index(&result, repo, + &ancestor, ours, &theirs, 0)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s(NULL, result.path); + cl_assert_equal_i(GIT_FILEMODE_UNREADABLE, result.mode); + + cl_assert_equal_i(0, result.len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__automerge_from_index_add_file(void) +{ + git_merge_file_result result = {0}; + git_index_entry *ancestor=NULL, ours, *theirs=NULL; + + git_oid_from_string(&ours.id, "d427e0b2e138501a3d15cc376077a3631e15bd46", GIT_OID_SHA1); + ours.path = "automergeable.txt"; + ours.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_merge_file_from_index(&result, repo, + ancestor, &ours, theirs, 0)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("automergeable.txt", result.path); + cl_assert_equal_i(GIT_FILEMODE_BLOB, result.mode); + + git_merge_file_result_free(&result); +} + void test_merge_files__automerge_whitespace_eol(void) { git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT,