I've noticed that in some cases a working copy can become inoperable after merge text conflict resolution with `svn_wc_conflict_choose_merged' option.
The problem can occur in the following scenario: 1. There is a text conflict, that is resolved with `svn_wc_conflict_choose_merged' option and specifying some path as MERGED_FILE. According to the API it can be any file, let's say "D:\merged.txt". 2. Previous step adds a FILE_INSTALL item into workqueue with a reference to "D:\merged.txt" and then runs the workqueue. 3. An error happens when running the workqueue. For example a working file cannot be overwritten because of 'Access denied' error. 4. The "D:\merged.txt" file gets deleted for some reason. 5. At this point the working copy is inoperable and cannot be fixed by 'svn cleanup', because workqueue contains absolute path to non-existing file ("D:\merged.txt"). I've attached a patch with fix for this problem. The patch contains a simplified regression test that reproduces problem by returning non-existing path from resolver callback. Log message: [[[ Make sure resolving a conflict with `svn_wc_conflict_choose_merged' can't break the workqueue by referencing a non-existing file * subversion/libsvn_wc/conflicts.c (build_text_conflict_resolve_items): Create private copy of MERGED_FILE contents in case of `svn_wc_conflict_choose_merged' is specified. Doing that to avoid referencing a potentially absent file in FILE_INSTALL workqueue record. Patch by: sergey.raevskiy{_AT_}visualsvn.com ]]]
Index: subversion/libsvn_wc/conflicts.c =================================================================== --- subversion/libsvn_wc/conflicts.c (revision 1900114) +++ subversion/libsvn_wc/conflicts.c (working copy) @@ -1706,9 +1706,49 @@ build_text_conflict_resolve_items(svn_skel_t **wor good to use". */ case svn_wc_conflict_choose_merged: { - install_from_abspath = merged_file - ? merged_file - : local_abspath; + if (merged_file) + { + /* Create private copy of merged MERGED_FILE contents to + avoid referencing a potentially absent file in FILE_INSTALL + workqueue record. */ + + const char *temp_dir_abspath; + const char *temp_file_abspath; + svn_stream_t *merged_contents; + svn_stream_t *tmp_contents; + apr_file_t *file; + + SVN_ERR(svn_io_file_open(&file, merged_file, + APR_READ, APR_OS_DEFAULT, + scratch_pool)); + merged_contents = svn_stream_from_aprfile2(file, FALSE, + scratch_pool); + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, + local_abspath, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_stream_open_unique(&tmp_contents, + &temp_file_abspath, + temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_stream_copy3(merged_contents, tmp_contents, + cancel_func, cancel_baton, + scratch_pool)); + + install_from_abspath = temp_file_abspath; + + /* Remove temp file after successful installation. */ + remove_source = TRUE; + } + else + { + /* Just reference the local file itself. */ + install_from_abspath = local_abspath; + } break; } case svn_wc_conflict_choose_postpone: Index: subversion/tests/libsvn_client/conflicts-test.c =================================================================== --- subversion/tests/libsvn_client/conflicts-test.c (revision 1900114) +++ subversion/tests/libsvn_client/conflicts-test.c (working copy) @@ -7459,7 +7459,110 @@ test_update_file_move_vs_file_move_accept_move(con return SVN_NO_ERROR; } +typedef struct { + const char *file_local_abspath; + const char *non_exising_file_abspath; +} choose_merged_non_existing_path_conflict_baton_t; +static svn_error_t * +choose_merged_non_existing_path_conflict_func( + svn_wc_conflict_result_t **result, + const svn_wc_conflict_description2_t *description, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + choose_merged_non_existing_path_conflict_baton_t *b = baton; + + if (strcmp(description->local_abspath, b->file_local_abspath) == 0 + && description->kind == svn_wc_conflict_kind_text) + { + /* 'Resolve' conflict by specifying non-existing file. */ + *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_merged, + b->non_exising_file_abspath, + result_pool); + + return SVN_NO_ERROR; + } + else + { + /* We should see only text conflict on 'newfile.txt'. */ + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "Unexpected call to conflict resolver func"); + } +} + +static svn_error_t * +test_resolve_choose_merged_non_existing_path(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + svn_boolean_t tree_conflicted; + choose_merged_non_existing_path_conflict_baton_t conflict_baton; + + SVN_ERR(svn_test__sandbox_create( + b, "test_resolve_choose_merged_non_existing_path", opts, pool)); + + SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool)); + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); /* r1 */ + + /* Create a simple text conflict on file. */ + + /* Write some content in text file. */ + SVN_ERR(sbox_file_write(b, "A/mu", "Original content.\n")); + SVN_ERR(sbox_wc_commit(b, "")); /* r2 */ + + /* Update text file. */ + SVN_ERR(sbox_file_write(b, "A/mu", "Modified content.\n")); + SVN_ERR(sbox_wc_commit(b, "")); /* r3 */ + + /* Update working copy to r2 and modify the file locally. */ + SVN_ERR(sbox_wc_update(b, "", 2)); + SVN_ERR(sbox_file_write(b, "A/mu", "Local modification.\n")); + + /* Update the working copy. */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + + /* Ensure that the expected text conflict is present. */ + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, "A/mu"), + ctx, b->pool, b->pool)); + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, b->pool, b->pool)); + SVN_TEST_ASSERT(text_conflicted && + props_conflicted->nelts == 0 && + !tree_conflicted); + + /* Set-up conflict resolver callback. */ + conflict_baton.file_local_abspath = sbox_wc_path(b, "A/mu"); + conflict_baton.non_exising_file_abspath = sbox_wc_path(b, "non-existing"); + ctx->conflict_func2 = choose_merged_non_existing_path_conflict_func; + ctx->conflict_baton2 = &conflict_baton; + + /* 'Resolve' the conflict -- get an error. */ + SVN_TEST_ASSERT_ERROR(svn_client_resolve(sbox_wc_path(b, "A/mu"), + svn_depth_empty, + svn_wc_conflict_choose_unspecified, + ctx, b->pool), + SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE); + + /* Clear the resolver callback. */ + ctx->conflict_func2 = NULL; + ctx->conflict_baton2 = NULL; + + /* Working copy is still operable; we shouldn't get any error here. */ + SVN_ERR(sbox_wc_revert(b, b->wc_abspath, svn_depth_infinity)); + + return SVN_NO_ERROR; +} + + /* ========================================================================== */ @@ -7588,6 +7691,8 @@ static struct svn_test_descriptor_t test_funcs[] = "file move vs file move during update"), SVN_TEST_OPTS_PASS(test_update_file_move_vs_file_move_accept_move, "file move vs file move during update accept move"), + SVN_TEST_OPTS_PASS(test_resolve_choose_merged_non_existing_path, + "resolve text conflict with non-existing path"), SVN_TEST_NULL };