diff --git a/src/checkout.c b/src/checkout.c index fa0609fe3..7c90ef81d 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -851,7 +851,7 @@ static void report_progress( data->opts.progress_payload); } -static int checkout_safe_for_update_only(const char *path, mode_t expected_mode) +int git_checkout__safe_for_update_only(const char *path, mode_t expected_mode) { struct stat st; @@ -921,7 +921,7 @@ static int checkout_blob( return -1; if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { - int rval = checkout_safe_for_update_only( + int rval = git_checkout__safe_for_update_only( git_buf_cstr(&data->path), file->mode); if (rval <= 0) return rval; diff --git a/src/checkout.h b/src/checkout.h index 27e682fe1..9a8098998 100644 --- a/src/checkout.h +++ b/src/checkout.h @@ -40,6 +40,10 @@ extern int git_checkout_iterator( git_iterator *target, const git_checkout_opts *opts); +int git_checkout__safe_for_update_only( + const char *path, + mode_t expected_mode); + int git_checkout__write_content( checkout_data *data, const git_oid *oid, diff --git a/src/checkout_conflicts.c b/src/checkout_conflicts.c index eb1ac6e14..aa9064752 100644 --- a/src/checkout_conflicts.c +++ b/src/checkout_conflicts.c @@ -410,6 +410,7 @@ static int checkout_write_entry( { const char *hint_path = NULL, *suffix; struct stat st; + int error; assert (side == conflict->ours || side == conflict->theirs); @@ -434,6 +435,10 @@ static int checkout_write_entry( hint_path = side->path; } + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = git_checkout__safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0) + return error; + return git_checkout__write_content(data, &side->oid, git_buf_cstr(&data->path), hint_path, side->mode, &st); } @@ -528,8 +533,14 @@ static int checkout_write_merge( goto done; } - if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0 || - (error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 || + if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) + goto done; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = git_checkout__safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0) + goto done; + + if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 || (error = git_filebuf_open(&output, path_workdir.ptr, GIT_FILEBUF_DO_NOT_BUFFER)) < 0 || (error = git_filebuf_write(&output, result.data, result.len)) < 0 || (error = git_filebuf_commit(&output, result.mode)) < 0) diff --git a/tests-clar/checkout/conflict.c b/tests-clar/checkout/conflict.c index 8b6dd4cd8..0b37ec8f5 100644 --- a/tests-clar/checkout/conflict.c +++ b/tests-clar/checkout/conflict.c @@ -82,7 +82,9 @@ static void create_index(struct checkout_index_entry *entries, size_t entries_le for (i = 0; i < entries_len; i++) { git_buf_joinpath(&path, TEST_REPO_PATH, entries[i].path); - p_unlink(git_buf_cstr(&path)); + + if (entries[i].stage == 3 && (i == 0 || strcmp(entries[i-1].path, entries[i].path) != 0 || entries[i-1].stage != 2)) + p_unlink(git_buf_cstr(&path)); git_index_remove_bypath(g_index, entries[i].path); } @@ -203,6 +205,7 @@ void test_checkout_conflict__ignored(void) opts.checkout_strategy |= GIT_CHECKOUT_SKIP_UNMERGED; create_conflicting_index(); + cl_git_pass(p_unlink(TEST_REPO_PATH "/conflicting.txt")); cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); @@ -974,3 +977,48 @@ void test_checkout_conflict__name_mangled_file_exists_in_workdir(void) ensure_workdir("directory_file-one~ours_0", 0100644, CONFLICTING_OURS_OID); ensure_workdir("directory_file-two~theirs_0", 0100644, CONFLICTING_THEIRS_OID); } + +void test_checkout_conflict__update_only(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "modify-delete" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "modify-delete" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" }, + { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" }, + { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_ONLY; + + create_index(checkout_index_entries, 3); + git_index_write(g_index); + + cl_git_pass(p_mkdir("merge-resolve/directory_file-two", 0777)); + cl_git_rewritefile("merge-resolve/directory_file-two/file", CONFLICTING_OURS_FILE); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE); + ensure_workdir("directory_file-two/file", 0100644, CONFLICTING_OURS_OID); + + cl_assert(!git_path_exists("merge-resolve/modify-delete")); + cl_assert(!git_path_exists("merge-resolve/test-one.txt")); + cl_assert(!git_path_exists("merge-resolve/test-one-side-one.txt")); + cl_assert(!git_path_exists("merge-resolve/test-one-side-two.txt")); + cl_assert(!git_path_exists("merge-resolve/test-one.txt~ours")); + cl_assert(!git_path_exists("merge-resolve/test-one.txt~theirs")); + cl_assert(!git_path_exists("merge-resolve/directory_file-one/file")); + cl_assert(!git_path_exists("merge-resolve/directory_file-one~ours")); + cl_assert(!git_path_exists("merge-resolve/directory_file-two~theirs")); +}