diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
commit | cf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch) | |
tree | da27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_wc/conflicts.c | |
parent | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff) | |
download | subversion-tarball-master.tar.gz |
subversion-1.9.7HEADsubversion-1.9.7master
Diffstat (limited to 'subversion/libsvn_wc/conflicts.c')
-rw-r--r-- | subversion/libsvn_wc/conflicts.c | 2022 |
1 files changed, 1101 insertions, 921 deletions
diff --git a/subversion/libsvn_wc/conflicts.c b/subversion/libsvn_wc/conflicts.c index f2d6f3e..f04c6de 100644 --- a/subversion/libsvn_wc/conflicts.c +++ b/subversion/libsvn_wc/conflicts.c @@ -477,8 +477,7 @@ svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel, hi; hi = apr_hash_next(hi)) { - svn_skel__prepend_str(apr_pstrdup(result_pool, - svn__apr_hash_index_key(hi)), + svn_skel__prepend_str(apr_pstrdup(result_pool, apr_hash_this_key(hi)), conflict_names, result_pool); } @@ -509,7 +508,7 @@ svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel, } /* A map for svn_wc_conflict_reason_t values. */ -static const svn_token_map_t local_change_map[] = +static const svn_token_map_t reason_map[] = { { "edited", svn_wc_conflict_reason_edited }, { "obstructed", svn_wc_conflict_reason_obstructed }, @@ -523,7 +522,7 @@ static const svn_token_map_t local_change_map[] = { NULL } }; -static const svn_token_map_t incoming_change_map[] = +static const svn_token_map_t action_map[] = { { "edited", svn_wc_conflict_action_edit }, { "added", svn_wc_conflict_action_add }, @@ -536,8 +535,8 @@ svn_error_t * svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, svn_wc__db_t *db, const char *wri_abspath, - svn_wc_conflict_reason_t local_change, - svn_wc_conflict_action_t incoming_change, + svn_wc_conflict_reason_t reason, + svn_wc_conflict_action_t action, const char *move_src_op_root_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) @@ -550,12 +549,12 @@ svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, SVN_ERR_ASSERT(!tree_conflict); /* ### Use proper error? */ - SVN_ERR_ASSERT(local_change == svn_wc_conflict_reason_moved_away + SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_moved_away || !move_src_op_root_abspath); /* ### Use proper error? */ tree_conflict = svn_skel__make_empty_list(result_pool); - if (local_change == svn_wc_conflict_reason_moved_away + if (reason == svn_wc_conflict_reason_moved_away && move_src_op_root_abspath) { const char *move_src_op_root_relpath; @@ -569,13 +568,11 @@ svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, result_pool); } - svn_skel__prepend_str( - svn_token__to_word(incoming_change_map, incoming_change), - tree_conflict, result_pool); + svn_skel__prepend_str(svn_token__to_word(action_map, action), + tree_conflict, result_pool); - svn_skel__prepend_str( - svn_token__to_word(local_change_map, local_change), - tree_conflict, result_pool); + svn_skel__prepend_str(svn_token__to_word(reason_map, reason), + tree_conflict, result_pool); /* Tree conflicts have no marker files */ markers = svn_skel__make_empty_list(result_pool); @@ -931,8 +928,8 @@ svn_wc__conflict_read_prop_conflict(const char **marker_abspath, } svn_error_t * -svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change, - svn_wc_conflict_action_t *incoming_change, +svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *reason, + svn_wc_conflict_action_t *action, const char **move_src_op_root_abspath, svn_wc__db_t *db, const char *wri_abspath, @@ -957,28 +954,28 @@ svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change, c = c->next; /* Skip markers */ { - int value = svn_token__from_mem(local_change_map, c->data, c->len); + int value = svn_token__from_mem(reason_map, c->data, c->len); - if (local_change) + if (reason) { if (value != SVN_TOKEN_UNKNOWN) - *local_change = value; + *reason = value; else - *local_change = svn_wc_conflict_reason_edited; + *reason = svn_wc_conflict_reason_edited; } is_moved_away = (value == svn_wc_conflict_reason_moved_away); } c = c->next; - if (incoming_change) + if (action) { - int value = svn_token__from_mem(incoming_change_map, c->data, c->len); + int value = svn_token__from_mem(action_map, c->data, c->len); if (value != SVN_TOKEN_UNKNOWN) - *incoming_change = value; + *action = value; else - *incoming_change = svn_wc_conflict_action_edit; + *action = svn_wc_conflict_action_edit; } c = c->next; @@ -1050,68 +1047,7 @@ svn_wc__conflict_read_markers(const apr_array_header_t **markers, /* -------------------------------------------------------------------- */ -/* Helper for svn_wc__conflict_create_markers */ -static svn_skel_t * -prop_conflict_skel_new(apr_pool_t *result_pool) -{ - svn_skel_t *operation = svn_skel__make_empty_list(result_pool); - svn_skel_t *result = svn_skel__make_empty_list(result_pool); - - svn_skel__prepend(operation, result); - return result; -} - - -/* Helper for prop_conflict_skel_add */ -static void -prepend_prop_value(const svn_string_t *value, - svn_skel_t *skel, - apr_pool_t *result_pool) -{ - svn_skel_t *value_skel = svn_skel__make_empty_list(result_pool); - - if (value != NULL) - { - const void *dup = apr_pmemdup(result_pool, value->data, value->len); - - svn_skel__prepend(svn_skel__mem_atom(dup, value->len, result_pool), - value_skel); - } - - svn_skel__prepend(value_skel, skel); -} - - -/* Helper for svn_wc__conflict_create_markers */ -static svn_error_t * -prop_conflict_skel_add( - svn_skel_t *skel, - const char *prop_name, - const svn_string_t *original_value, - const svn_string_t *mine_value, - const svn_string_t *incoming_value, - const svn_string_t *incoming_base_value, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_skel_t *prop_skel = svn_skel__make_empty_list(result_pool); - - /* ### check that OPERATION has been filled in. */ - - /* See notes/wc-ng/conflict-storage */ - prepend_prop_value(incoming_base_value, prop_skel, result_pool); - prepend_prop_value(incoming_value, prop_skel, result_pool); - prepend_prop_value(mine_value, prop_skel, result_pool); - prepend_prop_value(original_value, prop_skel, result_pool); - svn_skel__prepend_str(apr_pstrdup(result_pool, prop_name), prop_skel, - result_pool); - svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_PROP, prop_skel, result_pool); - /* Now we append PROP_SKEL to the end of the provided conflict SKEL. */ - svn_skel__append(skel, prop_skel); - - return SVN_NO_ERROR; -} svn_error_t * svn_wc__conflict_create_markers(svn_skel_t **work_items, @@ -1141,10 +1077,8 @@ svn_wc__conflict_create_markers(svn_skel_t **work_items, /* Ok, currently we have to do a few things for property conflicts: - Create a marker file - - Create a WQ item that sets the marker name - - Create a WQ item that fills the marker with the expected data - - This can be simplified once we really store conflict_skel in wc.db */ + - Store the name in the conflict_skel + - Create a WQ item that fills the marker with the expected data */ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); @@ -1176,65 +1110,9 @@ svn_wc__conflict_create_markers(svn_skel_t **work_items, svn_skel__prepend_str(marker_relpath, prop_conflict->children->next, result_pool); } - - /* Store the data in the WQ item in the same format used as 1.7. - Once we store the data in DB it is easier to just read it back - from the workqueue */ - { - svn_skel_t *prop_data; - apr_hash_index_t *hi; - apr_hash_t *old_props; - apr_hash_t *mine_props; - apr_hash_t *their_original_props; - apr_hash_t *their_props; - apr_hash_t *conflicted_props; - - SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL, - &mine_props, - &their_original_props, - &their_props, - &conflicted_props, - db, local_abspath, - conflict_skel, - scratch_pool, - scratch_pool)); - - if (operation == svn_wc_operation_merge) - SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, - scratch_pool, scratch_pool)); - else - old_props = their_original_props; - - prop_data = prop_conflict_skel_new(result_pool); - - for (hi = apr_hash_first(scratch_pool, conflicted_props); - hi; - hi = apr_hash_next(hi)) - { - const char *propname = svn__apr_hash_index_key(hi); - - SVN_ERR(prop_conflict_skel_add( - prop_data, propname, - old_props - ? svn_hash_gets(old_props, propname) - : NULL, - mine_props - ? svn_hash_gets(mine_props, propname) - : NULL, - their_props - ? svn_hash_gets(their_props, propname) - : NULL, - their_original_props - ? svn_hash_gets(their_original_props, propname) - : NULL, - result_pool, scratch_pool)); - } - - SVN_ERR(svn_wc__wq_build_prej_install(work_items, - db, local_abspath, - prop_data, - scratch_pool, scratch_pool)); - } + SVN_ERR(svn_wc__wq_build_prej_install(work_items, + db, local_abspath, + scratch_pool, scratch_pool)); } return SVN_NO_ERROR; @@ -1264,6 +1142,7 @@ static svn_error_t * generate_propconflict(svn_boolean_t *conflict_remains, svn_wc__db_t *db, const char *local_abspath, + svn_node_kind_t kind, svn_wc_operation_t operation, const svn_wc_conflict_version_t *left_version, const svn_wc_conflict_version_t *right_version, @@ -1274,29 +1153,18 @@ generate_propconflict(svn_boolean_t *conflict_remains, const svn_string_t *incoming_new_val, svn_wc_conflict_resolver_func2_t conflict_func, void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool) { svn_wc_conflict_result_t *result = NULL; svn_wc_conflict_description2_t *cdesc; const char *dirpath = svn_dirent_dirname(local_abspath, scratch_pool); - svn_node_kind_t kind; const svn_string_t *new_value = NULL; - SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, - FALSE /* allow_missing */, - FALSE /* show_deleted */, - FALSE /* show_hidden */, - scratch_pool)); - - if (kind == svn_node_none) - return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, - _("The node '%s' was not found."), - svn_dirent_local_style(local_abspath, - scratch_pool)); - cdesc = svn_wc_conflict_description_create_prop2( local_abspath, - (kind == svn_node_dir) ? svn_node_dir : svn_node_file, + kind, propname, scratch_pool); cdesc->operation = operation; @@ -1313,6 +1181,7 @@ generate_propconflict(svn_boolean_t *conflict_remains, svn_io_file_del_on_pool_cleanup, scratch_pool)); cdesc->my_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + cdesc->prop_value_working = working_val; } if (incoming_new_val) @@ -1323,7 +1192,11 @@ generate_propconflict(svn_boolean_t *conflict_remains, incoming_new_val->len, svn_io_file_del_on_pool_cleanup, scratch_pool)); - cdesc->their_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + + /* ### For property conflicts, cd2 stores prop_reject_abspath in + * ### their_abspath, and stores theirs_abspath in merged_file. */ + cdesc->merged_file = svn_dirent_join(dirpath, file_name, scratch_pool); + cdesc->prop_value_incoming_new = incoming_new_val; } if (!base_val && !incoming_old_val) @@ -1332,7 +1205,6 @@ generate_propconflict(svn_boolean_t *conflict_remains, base_file stay NULL as-is. Both agents are attempting to add a new property. */ } - else if ((base_val && !incoming_old_val) || (!base_val && incoming_old_val)) { @@ -1354,7 +1226,6 @@ generate_propconflict(svn_boolean_t *conflict_remains, scratch_pool)); cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); } - else /* base and old are both non-NULL */ { const svn_string_t *conflict_base_val; @@ -1391,6 +1262,9 @@ generate_propconflict(svn_boolean_t *conflict_remains, svn_io_file_del_on_pool_cleanup, scratch_pool)); cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + cdesc->prop_value_base = base_val; + cdesc->prop_value_incoming_old = incoming_old_val; + if (working_val && incoming_new_val) { svn_stream_t *mergestream; @@ -1398,17 +1272,22 @@ generate_propconflict(svn_boolean_t *conflict_remains, svn_diff_file_options_t *options = svn_diff_file_options_create(scratch_pool); - SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->merged_file, + SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->prop_reject_abspath, NULL, svn_io_file_del_on_pool_cleanup, scratch_pool, scratch_pool)); SVN_ERR(svn_diff_mem_string_diff3(&diff, conflict_base_val, working_val, incoming_new_val, options, scratch_pool)); - SVN_ERR(svn_diff_mem_string_output_merge2 - (mergestream, diff, conflict_base_val, working_val, + SVN_ERR(svn_diff_mem_string_output_merge3(mergestream, diff, + conflict_base_val, working_val, incoming_new_val, NULL, NULL, NULL, NULL, - svn_diff_conflict_display_modified_latest, scratch_pool)); + svn_diff_conflict_display_modified_latest, + cancel_func, cancel_baton, scratch_pool)); SVN_ERR(svn_stream_close(mergestream)); + + /* ### For property conflicts, cd2 stores prop_reject_abspath in + * ### their_abspath, and stores theirs_abspath in merged_file. */ + cdesc->their_abspath = cdesc->prop_reject_abspath; } } @@ -1427,10 +1306,8 @@ generate_propconflict(svn_boolean_t *conflict_remains, cdesc->reason = svn_wc_conflict_reason_edited; /* Invoke the interactive conflict callback. */ - { - SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, - scratch_pool)); - } + SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, + scratch_pool)); if (result == NULL) { *conflict_remains = TRUE; @@ -1476,18 +1353,24 @@ generate_propconflict(svn_boolean_t *conflict_remains, { svn_stringbuf_t *merged_stringbuf; - if (!cdesc->merged_file && !result->merged_file) + if (!cdesc->merged_file + && (!result->merged_file && !result->merged_value)) return svn_error_create (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("Conflict callback violated API:" " returned no merged file")); - SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf, - result->merged_file ? - result->merged_file : - cdesc->merged_file, - scratch_pool)); - new_value = svn_stringbuf__morph_into_string(merged_stringbuf); + if (result->merged_value) + new_value = result->merged_value; + else + { + SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf, + result->merged_file ? + result->merged_file : + cdesc->merged_file, + scratch_pool)); + new_value = svn_stringbuf__morph_into_string(merged_stringbuf); + } *conflict_remains = FALSE; break; } @@ -1513,166 +1396,113 @@ generate_propconflict(svn_boolean_t *conflict_remains, return SVN_NO_ERROR; } -/* Resolve the text conflict on DB/LOCAL_ABSPATH in the manner specified - * by CHOICE. +/* Perform a 3-way merge in which conflicts are expected, showing the + * conflicts in the way specified by STYLE, and using MERGE_OPTIONS. * - * Set *WORK_ITEMS to new work items that will make the on-disk changes - * needed to complete the resolution (but not to mark it as resolved). - * Set *IS_RESOLVED to true if the conflicts are resolved; otherwise - * (which is only if CHOICE is 'postpone') to false. + * The three input files are LEFT_ABSPATH (the base), DETRANSLATED_TARGET + * and RIGHT_ABSPATH. The output is stored in a new temporary file, + * whose name is put into *CHOSEN_ABSPATH. + * + * The output file will be deleted according to DELETE_WHEN. If + * DELETE_WHEN is 'on pool cleanup', it refers to RESULT_POOL. + * + * DB and WRI_ABSPATH are used to choose a directory for the output file. + * + * Allocate *CHOSEN_ABSPATH in RESULT_POOL. Use SCRATCH_POOL for temporary + * allocations. + */ +static svn_error_t * +merge_showing_conflicts(const char **chosen_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + svn_diff_conflict_display_style_t style, + const apr_array_header_t *merge_options, + const char *left_abspath, + const char *detranslated_target, + const char *right_abspath, + svn_io_file_del_t delete_when, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *temp_dir; + svn_stream_t *chosen_stream; + svn_diff_t *diff; + svn_diff_file_options_t *diff3_options; + + diff3_options = svn_diff_file_options_create(scratch_pool); + if (merge_options) + SVN_ERR(svn_diff_file_options_parse(diff3_options, + merge_options, + scratch_pool)); + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, + wri_abspath, + scratch_pool, scratch_pool)); + /* We need to open the stream in RESULT_POOL because that controls the + * lifetime of the file if DELETE_WHEN is 'on pool cleanup'. (We also + * want to allocate CHOSEN_ABSPATH in RESULT_POOL, but we don't care + * about the stream itself.) */ + SVN_ERR(svn_stream_open_unique(&chosen_stream, chosen_abspath, + temp_dir, delete_when, + result_pool, scratch_pool)); + SVN_ERR(svn_diff_file_diff3_2(&diff, + left_abspath, + detranslated_target, right_abspath, + diff3_options, scratch_pool)); + SVN_ERR(svn_diff_file_output_merge3(chosen_stream, diff, + left_abspath, + detranslated_target, + right_abspath, + NULL, NULL, NULL, NULL, /* markers */ + style, cancel_func, cancel_baton, + scratch_pool)); + SVN_ERR(svn_stream_close(chosen_stream)); + + return SVN_NO_ERROR; +} + +/* Prepare to delete an artifact file at ARTIFACT_FILE_ABSPATH in the + * working copy at DB/WRI_ABSPATH. * - * LEFT_ABSPATH, RIGHT_ABSPATH, and DETRANSLATED_TARGET are the - * input files to the 3-way merge that will be performed if CHOICE is - * 'theirs-conflict' or 'mine-conflict'. LEFT_ABSPATH is also the file - * that will be used if CHOICE is 'base', and RIGHT_ABSPATH if CHOICE is - * 'theirs-full'. MERGED_ABSPATH will be used if CHOICE is 'merged'. + * Set *WORK_ITEMS to a new work item that, when run, will delete the + * artifact file; or to NULL if there is no file to delete. * - * DETRANSLATED_TARGET is the detranslated version of 'mine' (see - * detranslate_wc_file() above). MERGE_OPTIONS are passed to the - * diff3 implementation in case a 3-way merge has to be carried out. + * Set *FILE_FOUND to TRUE if the artifact file is found on disk and its + * node kind is 'file'; otherwise do not change *FILE_FOUND. FILE_FOUND + * may be NULL if not required. */ static svn_error_t * -eval_text_conflict_func_result(svn_skel_t **work_items, - svn_boolean_t *is_resolved, +remove_artifact_file_if_exists(svn_skel_t **work_items, + svn_boolean_t *file_found, svn_wc__db_t *db, - const char *local_abspath, - svn_wc_conflict_choice_t choice, - const apr_array_header_t *merge_options, - const char *left_abspath, - const char *right_abspath, - const char *merged_abspath, - const char *detranslated_target, + const char *wri_abspath, + const char *artifact_file_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - const char *install_from_abspath = NULL; - svn_boolean_t remove_source = FALSE; - *work_items = NULL; - - switch (choice) + if (artifact_file_abspath) { - /* If the callback wants to use one of the fulltexts - to resolve the conflict, so be it.*/ - case svn_wc_conflict_choose_base: - { - install_from_abspath = left_abspath; - *is_resolved = TRUE; - break; - } - case svn_wc_conflict_choose_theirs_full: - { - install_from_abspath = right_abspath; - *is_resolved = TRUE; - break; - } - case svn_wc_conflict_choose_mine_full: - { - install_from_abspath = detranslated_target; - *is_resolved = TRUE; - break; - } - case svn_wc_conflict_choose_theirs_conflict: - case svn_wc_conflict_choose_mine_conflict: - { - const char *chosen_abspath; - const char *temp_dir; - svn_stream_t *chosen_stream; - svn_diff_t *diff; - svn_diff_conflict_display_style_t style; - svn_diff_file_options_t *diff3_options; - - diff3_options = svn_diff_file_options_create(scratch_pool); - - if (merge_options) - SVN_ERR(svn_diff_file_options_parse(diff3_options, - merge_options, - scratch_pool)); - - style = choice == svn_wc_conflict_choose_theirs_conflict - ? svn_diff_conflict_display_latest - : svn_diff_conflict_display_modified; - - SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, - local_abspath, - scratch_pool, scratch_pool)); - SVN_ERR(svn_stream_open_unique(&chosen_stream, &chosen_abspath, - temp_dir, svn_io_file_del_none, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_diff_file_diff3_2(&diff, - left_abspath, - detranslated_target, right_abspath, - diff3_options, scratch_pool)); - SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff, - left_abspath, - detranslated_target, - right_abspath, - /* markers ignored */ - NULL, NULL, - NULL, NULL, - style, - scratch_pool)); - SVN_ERR(svn_stream_close(chosen_stream)); - - install_from_abspath = chosen_abspath; - remove_source = TRUE; - *is_resolved = TRUE; - break; - } + svn_node_kind_t node_kind; - /* For the case of 3-way file merging, we don't - really distinguish between these return values; - if the callback claims to have "generally - resolved" the situation, we still interpret - that as "OK, we'll assume the merged version is - good to use". */ - case svn_wc_conflict_choose_merged: - { - install_from_abspath = merged_abspath; - *is_resolved = TRUE; - break; - } - case svn_wc_conflict_choose_postpone: - default: + SVN_ERR(svn_io_check_path(artifact_file_abspath, &node_kind, + scratch_pool)); + if (node_kind == svn_node_file) { - /* Assume conflict remains. */ - *is_resolved = FALSE; - return SVN_NO_ERROR; + SVN_ERR(svn_wc__wq_build_file_remove(work_items, + db, wri_abspath, + artifact_file_abspath, + result_pool, scratch_pool)); + if (file_found) + *file_found = TRUE; } } - SVN_ERR_ASSERT(install_from_abspath != NULL); - - { - svn_skel_t *work_item; - - SVN_ERR(svn_wc__wq_build_file_install(&work_item, - db, local_abspath, - install_from_abspath, - FALSE /* use_commit_times */, - FALSE /* record_fileinfo */, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - - SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - - if (remove_source) - { - SVN_ERR(svn_wc__wq_build_file_remove(&work_item, - db, local_abspath, - install_from_abspath, - result_pool, scratch_pool)); - *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); - } - } - return SVN_NO_ERROR; } - /* Create a new file in the same directory as LOCAL_ABSPATH, with the same basename as LOCAL_ABSPATH, with a ".edited" extension, and set *WORK_ITEM to a new work item that will copy and translate from the file @@ -1715,152 +1545,310 @@ save_merge_result(svn_skel_t **work_item, } -/* Call the conflict resolver callback for a text conflict, and resolve - * the conflict if it tells us to do so. - * - * Assume that there is a text conflict on the path DB/LOCAL_ABSPATH. + +/* Resolve the text conflict in CONFLICT, which is currently recorded + * on DB/LOCAL_ABSPATH in the manner specified by CHOICE. * - * Call CONFLICT_FUNC with CONFLICT_BATON to find out whether and how - * it wants to resolve the conflict. Pass it a conflict description - * containing OPERATION, LEFT/RIGHT_ABSPATH, LEFT/RIGHT_VERSION, - * RESULT_TARGET and DETRANSLATED_TARGET. + * Set *WORK_ITEMS to new work items that will make the on-disk changes + * needed to complete the resolution (but not to mark it as resolved). * - * If the callback returns a resolution other than 'postpone', then - * perform that requested resolution and prepare to mark the conflict - * as resolved. + * Set *FOUND_ARTIFACT to true if conflict markers are removed; otherwise + * (which is only if CHOICE is 'postpone') to false. * - * Return *WORK_ITEMS that will do the on-disk work required to complete - * the resolution (but not to mark the conflict as resolved), and set - * *WAS_RESOLVED to true, if it was resolved. Set *WORK_ITEMS to NULL - * and *WAS_RESOLVED to FALSE otherwise. + * CHOICE, MERGED_FILE and SAVE_MERGED are typically values provided by + * the conflict resolver. * - * RESULT_TARGET is the path to the merged file produced by the internal - * or external 3-way merge, which may contain conflict markers, in - * repository normal form. DETRANSLATED_TARGET is the 'mine' version of - * the file, also in RNF. + * MERGE_OPTIONS allows customizing the diff handling when using + * per hunk conflict resolving. */ static svn_error_t * -resolve_text_conflict(svn_skel_t **work_items, - svn_boolean_t *was_resolved, - svn_wc__db_t *db, - const char *local_abspath, - const apr_array_header_t *merge_options, - svn_wc_operation_t operation, - const char *left_abspath, - const char *right_abspath, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - const char *result_target, - const char *detranslated_target, - svn_wc_conflict_resolver_func2_t conflict_func, - void *conflict_baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +build_text_conflict_resolve_items(svn_skel_t **work_items, + svn_boolean_t *found_artifact, + svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict, + svn_wc_conflict_choice_t choice, + const char *merged_file, + svn_boolean_t save_merged, + const apr_array_header_t *merge_options, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_wc_conflict_result_t *result; + const char *mine_abspath; + const char *their_old_abspath; + const char *their_abspath; svn_skel_t *work_item; - svn_wc_conflict_description2_t *cdesc; - apr_hash_t *props; + const char *install_from_abspath = NULL; + svn_boolean_t remove_source = FALSE; *work_items = NULL; - *was_resolved = FALSE; - - /* Give the conflict resolution callback a chance to clean - up the conflicts before we mark the file 'conflicted' */ - - SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, - scratch_pool, scratch_pool)); - - cdesc = svn_wc_conflict_description_create_text2(local_abspath, - scratch_pool); - cdesc->is_binary = FALSE; - cdesc->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); - cdesc->base_abspath = left_abspath; - cdesc->their_abspath = right_abspath; - cdesc->my_abspath = detranslated_target; - cdesc->merged_file = result_target; - cdesc->operation = operation; - cdesc->src_left_version = left_version; - cdesc->src_right_version = right_version; - SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, - scratch_pool)); - if (result == NULL) - return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Conflict callback violated API:" - " returned no results")); + if (found_artifact) + *found_artifact = FALSE; + + SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, + &their_old_abspath, + &their_abspath, + db, local_abspath, + conflict, + scratch_pool, scratch_pool)); + + if (save_merged) + SVN_ERR(save_merge_result(work_items, + db, local_abspath, + merged_file + ? merged_file + : local_abspath, + result_pool, scratch_pool)); + + if (choice == svn_wc_conflict_choose_postpone) + return SVN_NO_ERROR; - if (result->save_merged) + switch (choice) { - SVN_ERR(save_merge_result(work_items, - db, local_abspath, - /* Look for callback's own - merged-file first: */ - result->merged_file - ? result->merged_file - : result_target, - result_pool, scratch_pool)); + /* If the callback wants to use one of the fulltexts + to resolve the conflict, so be it.*/ + case svn_wc_conflict_choose_base: + { + install_from_abspath = their_old_abspath; + break; + } + case svn_wc_conflict_choose_theirs_full: + { + install_from_abspath = their_abspath; + break; + } + case svn_wc_conflict_choose_mine_full: + { + /* In case of selecting to resolve the conflict choosing the full + own file, allow the text conflict resolution to just take the + existing local file if no merged file was present (case: binary + file conflicts do not generate a locally merge file). + */ + install_from_abspath = mine_abspath + ? mine_abspath + : local_abspath; + break; + } + case svn_wc_conflict_choose_theirs_conflict: + case svn_wc_conflict_choose_mine_conflict: + { + svn_diff_conflict_display_style_t style + = choice == svn_wc_conflict_choose_theirs_conflict + ? svn_diff_conflict_display_latest + : svn_diff_conflict_display_modified; + + if (mine_abspath == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Conflict on '%s' cannot be resolved to " + "'theirs-conflict' or 'mine-conflict' " + "because a merged version of the file " + "cannot be created."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + SVN_ERR(merge_showing_conflicts(&install_from_abspath, + db, local_abspath, + style, merge_options, + their_old_abspath, + mine_abspath, + their_abspath, + /* ### why not same as other caller? */ + svn_io_file_del_none, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + remove_source = TRUE; + break; + } + + /* For the case of 3-way file merging, we don't + really distinguish between these return values; + if the callback claims to have "generally + resolved" the situation, we still interpret + that as "OK, we'll assume the merged version is + good to use". */ + case svn_wc_conflict_choose_merged: + { + install_from_abspath = merged_file + ? merged_file + : local_abspath; + break; + } + case svn_wc_conflict_choose_postpone: + { + /* Assume conflict remains. */ + return SVN_NO_ERROR; + } + default: + SVN_ERR_ASSERT(choice == svn_wc_conflict_choose_postpone); } - if (result->choice != svn_wc_conflict_choose_postpone) + if (install_from_abspath == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Conflict on '%s' could not be resolved " + "because the chosen version of the file " + "is not available."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + /* ### It would be nice if we could somehow pass RECORD_FILEINFO + as true in some easy cases. */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + db, local_abspath, + install_from_abspath, + FALSE /* use_commit_times */, + FALSE /* record_fileinfo */, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + if (remove_source) { - SVN_ERR(eval_text_conflict_func_result(&work_item, - was_resolved, - db, local_abspath, - result->choice, - merge_options, - left_abspath, - right_abspath, - /* ### Sure this is an abspath? */ - result->merged_file - ? result->merged_file - : result_target, - detranslated_target, - result_pool, scratch_pool)); + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, local_abspath, + install_from_abspath, + result_pool, scratch_pool)); *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); } - else - *was_resolved = FALSE; + + SVN_ERR(remove_artifact_file_if_exists(&work_item, found_artifact, + db, local_abspath, + their_old_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + SVN_ERR(remove_artifact_file_if_exists(&work_item, found_artifact, + db, local_abspath, + their_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + SVN_ERR(remove_artifact_file_if_exists(&work_item, found_artifact, + db, local_abspath, + mine_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); return SVN_NO_ERROR; } +/* Set *DESC to a new description of the text conflict in + * CONFLICT_SKEL. If there is no text conflict in CONFLICT_SKEL, return + * an error. + * + * Use OPERATION and shallow copies of LEFT_VERSION and RIGHT_VERSION, + * rather than reading them from CONFLICT_SKEL. Use IS_BINARY and + * MIME_TYPE for the corresponding fields of *DESC. + * + * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary + * allocations. */ static svn_error_t * -setup_tree_conflict_desc(svn_wc_conflict_description2_t **desc, - svn_wc__db_t *db, - const char *local_abspath, - svn_wc_operation_t operation, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - svn_wc_conflict_reason_t local_change, - svn_wc_conflict_action_t incoming_change, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +read_text_conflict_desc(svn_wc_conflict_description2_t **desc, + svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + const char *mime_type, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *desc = svn_wc_conflict_description_create_text2(local_abspath, result_pool); + (*desc)->mime_type = mime_type; + (*desc)->is_binary = mime_type ? svn_mime_type_is_binary(mime_type) : FALSE; + (*desc)->operation = operation; + (*desc)->src_left_version = left_version; + (*desc)->src_right_version = right_version; + + SVN_ERR(svn_wc__conflict_read_text_conflict(&(*desc)->my_abspath, + &(*desc)->base_abspath, + &(*desc)->their_abspath, + db, local_abspath, + conflict_skel, + result_pool, scratch_pool)); + (*desc)->merged_file = apr_pstrdup(result_pool, local_abspath); + + return SVN_NO_ERROR; +} + +/* Set *CONFLICT_DESC to a new description of the tree conflict in + * CONFLICT_SKEL. If there is no tree conflict in CONFLICT_SKEL, return + * an error. + * + * Use OPERATION and shallow copies of LEFT_VERSION and RIGHT_VERSION, + * rather than reading them from CONFLICT_SKEL. + * + * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary + * allocations. */ +static svn_error_t * +read_tree_conflict_desc(svn_wc_conflict_description2_t **desc, + svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t node_kind, + const svn_skel_t *conflict_skel, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_node_kind_t tc_kind; + svn_node_kind_t local_kind; + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; - if (left_version) - tc_kind = left_version->node_kind; - else if (right_version) - tc_kind = right_version->node_kind; + SVN_ERR(svn_wc__conflict_read_tree_conflict( + &reason, &action, NULL, + db, local_abspath, conflict_skel, scratch_pool, scratch_pool)); + + if (reason == svn_wc_conflict_reason_missing) + local_kind = svn_node_none; + else if (reason == svn_wc_conflict_reason_unversioned || + reason == svn_wc_conflict_reason_obstructed) + SVN_ERR(svn_io_check_path(local_abspath, &local_kind, scratch_pool)); + else if (action == svn_wc_conflict_action_delete + && left_version + && (operation == svn_wc_operation_update + ||operation == svn_wc_operation_switch) + && (reason == svn_wc_conflict_reason_deleted + || reason == svn_wc_conflict_reason_moved_away)) + { + /* We have nothing locally to take the kind from */ + local_kind = left_version->node_kind; + } else - tc_kind = svn_node_file; /* Avoid assertion */ + local_kind = node_kind; - *desc = svn_wc_conflict_description_create_tree2(local_abspath, tc_kind, + *desc = svn_wc_conflict_description_create_tree2(local_abspath, local_kind, operation, left_version, right_version, result_pool); - (*desc)->reason = local_change; - (*desc)->action = incoming_change; + (*desc)->reason = reason; + (*desc)->action = action; return SVN_NO_ERROR; } +/* Forward definition */ +static svn_error_t * +resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, + svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict, + svn_wc_conflict_choice_t conflict_choice, + apr_hash_t *resolve_later, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); svn_error_t * svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, const char *local_abspath, + svn_node_kind_t kind, const svn_skel_t *conflict_skel, const apr_array_header_t *merge_options, svn_wc_conflict_resolver_func2_t resolver_func, @@ -1928,7 +1916,7 @@ svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, hi; hi = apr_hash_next(hi)) { - const char *propname = svn__apr_hash_index_key(hi); + const char *propname = apr_hash_this_key(hi); svn_boolean_t conflict_remains = TRUE; svn_pool_clear(iterpool); @@ -1937,7 +1925,7 @@ svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, SVN_ERR(cancel_func(cancel_baton)); SVN_ERR(generate_propconflict(&conflict_remains, - db, local_abspath, + db, local_abspath, kind, operation, left_version, right_version, @@ -1955,6 +1943,7 @@ svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, ? svn_hash_gets(their_props, propname) : NULL, resolver_func, resolver_baton, + cancel_func, cancel_baton, iterpool)); if (conflict_remains) @@ -1966,75 +1955,96 @@ svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, SVN_ERR(svn_wc__mark_resolved_prop_conflicts(db, local_abspath, scratch_pool)); } + svn_pool_destroy(iterpool); } if (text_conflicted) { - const char *mine_abspath; - const char *their_original_abspath; - const char *their_abspath; svn_skel_t *work_items; svn_boolean_t was_resolved; + svn_wc_conflict_description2_t *desc; + apr_hash_t *props; + svn_wc_conflict_result_t *result; - SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, - &their_original_abspath, - &their_abspath, - db, local_abspath, - conflict_skel, - scratch_pool, scratch_pool)); - - SVN_ERR(resolve_text_conflict(&work_items, &was_resolved, - db, local_abspath, - merge_options, - operation, - their_original_abspath, their_abspath, - left_version, right_version, - local_abspath, mine_abspath, - resolver_func, resolver_baton, + SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool, scratch_pool)); - if (was_resolved) + SVN_ERR(read_text_conflict_desc(&desc, + db, local_abspath, conflict_skel, + svn_prop_get_value(props, + SVN_PROP_MIME_TYPE), + operation, left_version, right_version, + scratch_pool, scratch_pool)); + + + work_items = NULL; + was_resolved = FALSE; + + /* Give the conflict resolution callback a chance to clean + up the conflicts before we mark the file 'conflicted' */ + + SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool, + scratch_pool)); + if (result == NULL) + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Conflict callback violated API:" + " returned no results")); + + SVN_ERR(build_text_conflict_resolve_items(&work_items, &was_resolved, + db, local_abspath, + conflict_skel, result->choice, + result->merged_file, + result->save_merged, + merge_options, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + if (result->choice != svn_wc_conflict_choose_postpone) { - if (work_items) - { - SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_items, - scratch_pool)); - SVN_ERR(svn_wc__wq_run(db, local_abspath, - cancel_func, cancel_baton, - scratch_pool)); - } - SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, - scratch_pool)); + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, + TRUE, FALSE, FALSE, + work_items, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); } } if (tree_conflicted) { - svn_wc_conflict_reason_t local_change; - svn_wc_conflict_action_t incoming_change; svn_wc_conflict_result_t *result; svn_wc_conflict_description2_t *desc; + svn_boolean_t resolved; + svn_node_kind_t node_kind; - SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change, - &incoming_change, - NULL, - db, local_abspath, - conflict_skel, - scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_read_kind(&node_kind, db, local_abspath, TRUE, + TRUE, FALSE, scratch_pool)); - SVN_ERR(setup_tree_conflict_desc(&desc, - db, local_abspath, - operation, left_version, right_version, - local_change, incoming_change, - scratch_pool, scratch_pool)); + SVN_ERR(read_tree_conflict_desc(&desc, + db, local_abspath, node_kind, + conflict_skel, + operation, left_version, right_version, + scratch_pool, scratch_pool)); /* Tell the resolver func about this conflict. */ SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool, scratch_pool)); - /* Ignore the result. We cannot apply it here since this code runs - * during an update or merge operation. Tree conflicts are always - * postponed and resolved after the operation has completed. */ + if (result == NULL) + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Conflict callback violated API:" + " returned no results")); + + /* Pass retry hash to avoid erroring out on cases where update + can continue safely. ### Need notify handling */ + if (result->choice != svn_wc_conflict_choose_postpone) + SVN_ERR(resolve_tree_conflict_on_node(&resolved, + db, local_abspath, conflict_skel, + result->choice, + apr_hash_make(scratch_pool), + NULL, NULL, /* ### notify */ + cancel_func, cancel_baton, + scratch_pool)); } return SVN_NO_ERROR; @@ -2042,7 +2052,8 @@ svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, /* Read all property conflicts contained in CONFLICT_SKEL into * individual conflict descriptions, and append those descriptions - * to the CONFLICTS array. + * to the CONFLICTS array. If there is no property conflict in + * CONFLICT_SKEL, return an error. * * If NOT create_tempfiles, always create a legacy property conflict * descriptor. @@ -2053,27 +2064,36 @@ svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary * allocations. */ static svn_error_t * -read_prop_conflicts(apr_array_header_t *conflicts, - svn_wc__db_t *db, - const char *local_abspath, - svn_skel_t *conflict_skel, - svn_boolean_t create_tempfiles, - svn_node_kind_t node_kind, - svn_wc_operation_t operation, - const svn_wc_conflict_version_t *left_version, - const svn_wc_conflict_version_t *right_version, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +read_prop_conflict_descs(apr_array_header_t *conflicts, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + svn_boolean_t create_tempfiles, + svn_node_kind_t node_kind, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - const char *prop_reject_file; + const char *prop_reject_abspath; + apr_hash_t *base_props; apr_hash_t *my_props; apr_hash_t *their_old_props; apr_hash_t *their_props; apr_hash_t *conflicted_props; apr_hash_index_t *hi; apr_pool_t *iterpool; + svn_boolean_t prop_conflicted; - SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file, + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, &prop_conflicted, + NULL, db, local_abspath, conflict_skel, + scratch_pool, scratch_pool)); + + if (!prop_conflicted) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_abspath, &my_props, &their_old_props, &their_props, @@ -2082,7 +2102,9 @@ read_prop_conflicts(apr_array_header_t *conflicts, conflict_skel, scratch_pool, scratch_pool)); - if ((! create_tempfiles) || apr_hash_count(conflicted_props) == 0) + prop_reject_abspath = apr_pstrdup(result_pool, prop_reject_abspath); + + if (apr_hash_count(conflicted_props) == 0) { /* Legacy prop conflict with only a .reject file. */ svn_wc_conflict_description2_t *desc; @@ -2091,26 +2113,31 @@ read_prop_conflicts(apr_array_header_t *conflicts, node_kind, "", result_pool); - /* ### This should be changed. The prej file should be stored - * ### separately from the other files. We need to rev the - * ### conflict description struct for this. */ - desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file); + /* ### For property conflicts, cd2 stores prop_reject_abspath in + * ### their_abspath, and stores theirs_abspath in merged_file. */ + desc->prop_reject_abspath = prop_reject_abspath; /* in result_pool */ + desc->their_abspath = desc->prop_reject_abspath; desc->operation = operation; desc->src_left_version = left_version; desc->src_right_version = right_version; - APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc; + APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t *) = desc; return SVN_NO_ERROR; } + if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__db_read_pristine_props(&base_props, db, local_abspath, + result_pool, scratch_pool)); + else + base_props = NULL; iterpool = svn_pool_create(scratch_pool); for (hi = apr_hash_first(scratch_pool, conflicted_props); hi; hi = apr_hash_next(hi)) { - const char *propname = svn__apr_hash_index_key(hi); + const char *propname = apr_hash_this_key(hi); svn_string_t *old_value; svn_string_t *my_value; svn_string_t *their_value; @@ -2118,10 +2145,10 @@ read_prop_conflicts(apr_array_header_t *conflicts, svn_pool_clear(iterpool); - desc = svn_wc_conflict_description_create_prop2(local_abspath, - node_kind, - propname, - result_pool); + desc = svn_wc_conflict_description_create_prop2(local_abspath, + node_kind, + propname, + result_pool); desc->operation = operation; desc->src_left_version = left_version; @@ -2149,26 +2176,30 @@ read_prop_conflicts(apr_array_header_t *conflicts, else desc->reason = svn_wc_conflict_reason_edited; - /* ### This should be changed. The prej file should be stored - * ### separately from the other files. We need to rev the - * ### conflict description struct for this. */ - desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file); + /* ### For property conflicts, cd2 stores prop_reject_abspath in + * ### their_abspath, and stores theirs_abspath in merged_file. */ + desc->prop_reject_abspath = prop_reject_abspath; /* in result_pool */ + desc->their_abspath = desc->prop_reject_abspath; + + desc->prop_value_base = base_props ? svn_hash_gets(base_props, propname) + : desc->prop_value_incoming_old; - /* ### This should be changed. The conflict description for - * ### props should contain these values as svn_string_t, - * ### rather than in temporary files. We need to rev the - * ### conflict description struct for this. */ if (my_value) { svn_stream_t *s; apr_size_t len; - SVN_ERR(svn_stream_open_unique(&s, &desc->my_abspath, NULL, - svn_io_file_del_on_pool_cleanup, - result_pool, iterpool)); - len = my_value->len; - SVN_ERR(svn_stream_write(s, my_value->data, &len)); - SVN_ERR(svn_stream_close(s)); + if (create_tempfiles) + { + SVN_ERR(svn_stream_open_unique(&s, &desc->my_abspath, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = my_value->len; + SVN_ERR(svn_stream_write(s, my_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + desc->prop_value_working = svn_string_dup(my_value, result_pool); } if (their_value) @@ -2176,15 +2207,19 @@ read_prop_conflicts(apr_array_header_t *conflicts, svn_stream_t *s; apr_size_t len; - /* ### Currently, their_abspath is used for the prop reject file. - * ### Put their value into merged instead... - * ### We need to rev the conflict description struct to fix this. */ - SVN_ERR(svn_stream_open_unique(&s, &desc->merged_file, NULL, - svn_io_file_del_on_pool_cleanup, - result_pool, iterpool)); - len = their_value->len; - SVN_ERR(svn_stream_write(s, their_value->data, &len)); - SVN_ERR(svn_stream_close(s)); + /* ### For property conflicts, cd2 stores prop_reject_abspath in + * ### their_abspath, and stores theirs_abspath in merged_file. */ + if (create_tempfiles) + { + SVN_ERR(svn_stream_open_unique(&s, &desc->merged_file, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = their_value->len; + SVN_ERR(svn_stream_write(s, their_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + desc->prop_value_incoming_new = svn_string_dup(their_value, result_pool); } if (old_value) @@ -2192,15 +2227,20 @@ read_prop_conflicts(apr_array_header_t *conflicts, svn_stream_t *s; apr_size_t len; - SVN_ERR(svn_stream_open_unique(&s, &desc->base_abspath, NULL, - svn_io_file_del_on_pool_cleanup, - result_pool, iterpool)); - len = old_value->len; - SVN_ERR(svn_stream_write(s, old_value->data, &len)); - SVN_ERR(svn_stream_close(s)); + if (create_tempfiles) + { + SVN_ERR(svn_stream_open_unique(&s, &desc->base_abspath, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = old_value->len; + SVN_ERR(svn_stream_write(s, old_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + desc->prop_value_incoming_old = svn_string_dup(old_value, result_pool); } - APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc; + APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t *) = desc; } svn_pool_destroy(iterpool); @@ -2209,13 +2249,15 @@ read_prop_conflicts(apr_array_header_t *conflicts, svn_error_t * svn_wc__read_conflicts(const apr_array_header_t **conflicts, + svn_skel_t **conflict_skel, svn_wc__db_t *db, const char *local_abspath, svn_boolean_t create_tempfiles, + svn_boolean_t only_tree_conflict, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_skel_t *conflict_skel; + svn_skel_t *the_conflict_skel; apr_array_header_t *cflcts; svn_boolean_t prop_conflicted; svn_boolean_t text_conflicted; @@ -2224,82 +2266,71 @@ svn_wc__read_conflicts(const apr_array_header_t **conflicts, const apr_array_header_t *locations; const svn_wc_conflict_version_t *left_version = NULL; const svn_wc_conflict_version_t *right_version = NULL; - - SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db, local_abspath, - scratch_pool, scratch_pool)); + svn_node_kind_t node_kind; + apr_hash_t *props; if (!conflict_skel) + conflict_skel = &the_conflict_skel; + + SVN_ERR(svn_wc__db_read_conflict(conflict_skel, &node_kind, &props, + db, local_abspath, + (conflict_skel == &the_conflict_skel) + ? scratch_pool + : result_pool, + scratch_pool)); + + if (!*conflict_skel) { /* Some callers expect not NULL */ *conflicts = apr_array_make(result_pool, 0, - sizeof(svn_wc_conflict_description2_t*));; + sizeof(svn_wc_conflict_description2_t *)); return SVN_NO_ERROR; } SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, &text_conflicted, &prop_conflicted, &tree_conflicted, - db, local_abspath, conflict_skel, + db, local_abspath, *conflict_skel, result_pool, scratch_pool)); cflcts = apr_array_make(result_pool, 4, - sizeof(svn_wc_conflict_description2_t*)); + sizeof(svn_wc_conflict_description2_t *)); if (locations && locations->nelts > 0) left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *); if (locations && locations->nelts > 1) right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *); - if (prop_conflicted) + if (prop_conflicted && !only_tree_conflict) { - svn_node_kind_t node_kind - = left_version ? left_version->node_kind : svn_node_unknown; - - SVN_ERR(read_prop_conflicts(cflcts, db, local_abspath, conflict_skel, - create_tempfiles, node_kind, - operation, left_version, right_version, - result_pool, scratch_pool)); + SVN_ERR(read_prop_conflict_descs(cflcts, + db, local_abspath, *conflict_skel, + create_tempfiles, node_kind, + operation, left_version, right_version, + result_pool, scratch_pool)); } - if (text_conflicted) + if (text_conflicted && !only_tree_conflict) { svn_wc_conflict_description2_t *desc; - desc = svn_wc_conflict_description_create_text2(local_abspath, - result_pool); - desc->operation = operation; - desc->src_left_version = left_version; - desc->src_right_version = right_version; - - SVN_ERR(svn_wc__conflict_read_text_conflict(&desc->my_abspath, - &desc->base_abspath, - &desc->their_abspath, - db, local_abspath, - conflict_skel, - result_pool, scratch_pool)); - - desc->merged_file = apr_pstrdup(result_pool, local_abspath); - - APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t*) = desc; + SVN_ERR(read_text_conflict_desc(&desc, + db, local_abspath, *conflict_skel, + svn_prop_get_value(props, + SVN_PROP_MIME_TYPE), + operation, left_version, right_version, + result_pool, scratch_pool)); + APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t *) = desc; } if (tree_conflicted) { - svn_wc_conflict_reason_t local_change; - svn_wc_conflict_action_t incoming_change; svn_wc_conflict_description2_t *desc; - SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change, - &incoming_change, - NULL, - db, local_abspath, - conflict_skel, - scratch_pool, scratch_pool)); - - SVN_ERR(setup_tree_conflict_desc(&desc, - db, local_abspath, - operation, left_version, right_version, - local_change, incoming_change, - result_pool, scratch_pool)); + SVN_ERR(read_tree_conflict_desc(&desc, + db, local_abspath, node_kind, + *conflict_skel, + operation, left_version, right_version, + result_pool, scratch_pool)); APR_ARRAY_PUSH(cflcts, const svn_wc_conflict_description2_t *) = desc; } @@ -2311,206 +2342,6 @@ svn_wc__read_conflicts(const apr_array_header_t **conflicts, /*** Resolving a conflict automatically ***/ -/* Prepare to delete an artifact file at ARTIFACT_FILE_ABSPATH in the - * working copy at DB/WRI_ABSPATH. - * - * Set *WORK_ITEMS to a new work item that, when run, will delete the - * artifact file; or to NULL if there is no file to delete. - * - * Set *FILE_FOUND to TRUE if the artifact file is found on disk and its - * node kind is 'file'; otherwise do not change *FILE_FOUND. FILE_FOUND - * may be NULL if not required. - */ -static svn_error_t * -remove_artifact_file_if_exists(svn_skel_t **work_items, - svn_boolean_t *file_found, - svn_wc__db_t *db, - const char *wri_abspath, - const char *artifact_file_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - *work_items = NULL; - if (artifact_file_abspath) - { - svn_node_kind_t node_kind; - - SVN_ERR(svn_io_check_path(artifact_file_abspath, &node_kind, - scratch_pool)); - if (node_kind == svn_node_file) - { - SVN_ERR(svn_wc__wq_build_file_remove(work_items, - db, wri_abspath, - artifact_file_abspath, - result_pool, scratch_pool)); - if (file_found) - *file_found = TRUE; - } - } - - return SVN_NO_ERROR; -} - -/* - * Resolve the text conflict found in DB/LOCAL_ABSPATH according - * to CONFLICT_CHOICE. - * - * It is not an error if there is no text conflict. If a text conflict - * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. - * - * Note: When there are no conflict markers to remove there is no existing - * text conflict; just a database containing old information, which we should - * remove to avoid checking all the time. Resolving a text conflict by - * removing all the marker files is a fully supported scenario since - * Subversion 1.0. - */ -static svn_error_t * -resolve_text_conflict_on_node(svn_boolean_t *did_resolve, - svn_wc__db_t *db, - const char *local_abspath, - svn_wc_conflict_choice_t conflict_choice, - const char *merged_file, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - const char *conflict_old = NULL; - const char *conflict_new = NULL; - const char *conflict_working = NULL; - const char *auto_resolve_src; - svn_skel_t *work_item; - svn_skel_t *work_items = NULL; - svn_skel_t *conflicts; - svn_wc_operation_t operation; - svn_boolean_t text_conflicted; - - *did_resolve = FALSE; - - SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, - scratch_pool, scratch_pool)); - if (!conflicts) - return SVN_NO_ERROR; - - SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, &text_conflicted, - NULL, NULL, db, local_abspath, conflicts, - scratch_pool, scratch_pool)); - if (!text_conflicted) - return SVN_NO_ERROR; - - SVN_ERR(svn_wc__conflict_read_text_conflict(&conflict_working, - &conflict_old, - &conflict_new, - db, local_abspath, conflicts, - scratch_pool, scratch_pool)); - - /* Handle automatic conflict resolution before the temporary files are - * deleted, if necessary. */ - switch (conflict_choice) - { - case svn_wc_conflict_choose_base: - auto_resolve_src = conflict_old; - break; - case svn_wc_conflict_choose_mine_full: - auto_resolve_src = conflict_working; - break; - case svn_wc_conflict_choose_theirs_full: - auto_resolve_src = conflict_new; - break; - case svn_wc_conflict_choose_merged: - auto_resolve_src = merged_file; - break; - case svn_wc_conflict_choose_theirs_conflict: - case svn_wc_conflict_choose_mine_conflict: - { - if (conflict_old && conflict_working && conflict_new) - { - const char *temp_dir; - svn_stream_t *tmp_stream = NULL; - svn_diff_t *diff; - svn_diff_conflict_display_style_t style = - conflict_choice == svn_wc_conflict_choose_theirs_conflict - ? svn_diff_conflict_display_latest - : svn_diff_conflict_display_modified; - - SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, - local_abspath, - scratch_pool, - scratch_pool)); - SVN_ERR(svn_stream_open_unique(&tmp_stream, - &auto_resolve_src, - temp_dir, - svn_io_file_del_on_pool_cleanup, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_diff_file_diff3_2(&diff, - conflict_old, - conflict_working, - conflict_new, - svn_diff_file_options_create( - scratch_pool), - scratch_pool)); - SVN_ERR(svn_diff_file_output_merge2(tmp_stream, diff, - conflict_old, - conflict_working, - conflict_new, - /* markers ignored */ - NULL, NULL, NULL, NULL, - style, - scratch_pool)); - SVN_ERR(svn_stream_close(tmp_stream)); - } - else - auto_resolve_src = NULL; - break; - } - default: - return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, - _("Invalid 'conflict_result' argument")); - } - - if (auto_resolve_src) - { - SVN_ERR(svn_wc__wq_build_file_copy_translated( - &work_item, db, local_abspath, - auto_resolve_src, local_abspath, scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - - SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, - local_abspath, - scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - } - - /* Legacy behavior: Only report text conflicts as resolved when at least - one conflict marker file exists. - - If not the UI shows the conflict as already resolved - (and in this case we just remove the in-db conflict) */ - - SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, - db, local_abspath, conflict_old, - scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - - SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, - db, local_abspath, conflict_new, - scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - - SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, - db, local_abspath, conflict_working, - scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - - SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, - TRUE, FALSE, FALSE, - work_items, scratch_pool)); - SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, - scratch_pool)); - - return SVN_NO_ERROR; -} - /* * Resolve the property conflicts found in DB/LOCAL_ABSPATH according * to CONFLICT_CHOICE. @@ -2550,9 +2381,11 @@ static svn_error_t * resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, svn_wc__db_t *db, const char *local_abspath, + svn_skel_t *conflicts, const char *conflicted_propname, svn_wc_conflict_choice_t conflict_choice, const char *merged_file, + const svn_string_t *merged_value, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) @@ -2565,18 +2398,13 @@ resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, apr_hash_t *old_props; apr_hash_t *resolve_from = NULL; svn_skel_t *work_items = NULL; - svn_skel_t *conflicts; svn_wc_operation_t operation; svn_boolean_t prop_conflicted; + apr_hash_t *actual_props; + svn_boolean_t resolved_all, resolved_all_prop; *did_resolve = FALSE; - SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, - scratch_pool, scratch_pool)); - - if (!conflicts) - return SVN_NO_ERROR; - SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, &prop_conflicted, NULL, db, local_abspath, conflicts, scratch_pool, scratch_pool)); @@ -2589,12 +2417,35 @@ resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, db, local_abspath, conflicts, scratch_pool, scratch_pool)); + if (!conflicted_props) + { + /* We have a pre 1.8 property conflict. Just mark it resolved */ + + SVN_ERR(remove_artifact_file_if_exists(&work_items, did_resolve, + db, local_abspath, prop_reject_file, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, TRUE, FALSE, + work_items, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; + } + + if (conflicted_propname[0] != '\0' + && !svn_hash_gets(conflicted_props, conflicted_propname)) + { + return SVN_NO_ERROR; /* This property is not conflicted! */ + } + if (operation == svn_wc_operation_merge) SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, scratch_pool, scratch_pool)); else old_props = their_old_props; + SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, + scratch_pool, scratch_pool)); + /* We currently handle *_conflict as *_full as this argument is currently always applied for all conflicts on a node at the same time. Giving an error would break some tests that assumed that this would just @@ -2620,21 +2471,23 @@ resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, resolve_from = their_props; break; case svn_wc_conflict_choose_merged: - if (merged_file && conflicted_propname[0] != '\0') + if ((merged_file || merged_value) && conflicted_propname[0] != '\0') { - apr_hash_t *actual_props; - svn_stream_t *stream; - svn_string_t *merged_propval; + resolve_from = apr_hash_copy(scratch_pool, actual_props); - SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, - scratch_pool, scratch_pool)); - resolve_from = actual_props; + if (!merged_value) + { + svn_stream_t *stream; + svn_string_t *merged_propval; - SVN_ERR(svn_stream_open_readonly(&stream, merged_file, - scratch_pool, scratch_pool)); - SVN_ERR(svn_string_from_stream(&merged_propval, stream, - scratch_pool, scratch_pool)); - svn_hash_sets(resolve_from, conflicted_propname, merged_propval); + SVN_ERR(svn_stream_open_readonly(&stream, merged_file, + scratch_pool, scratch_pool)); + SVN_ERR(svn_string_from_stream(&merged_propval, stream, + scratch_pool, scratch_pool)); + + merged_value = merged_propval; + } + svn_hash_sets(resolve_from, conflicted_propname, merged_value); } else resolve_from = NULL; @@ -2644,47 +2497,97 @@ resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, _("Invalid 'conflict_result' argument")); } - if (conflicted_props && apr_hash_count(conflicted_props) && resolve_from) + + if (resolve_from) { apr_hash_index_t *hi; - apr_hash_t *actual_props; + apr_hash_t *apply_on_props; - SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, - scratch_pool, scratch_pool)); + if (conflicted_propname[0] == '\0') + { + /* Apply to all conflicted properties */ + apply_on_props = conflicted_props; + } + else + { + /* Apply to a single property */ + apply_on_props = apr_hash_make(scratch_pool); + svn_hash_sets(apply_on_props, conflicted_propname, ""); + } - for (hi = apr_hash_first(scratch_pool, conflicted_props); + /* Apply the selected changes */ + for (hi = apr_hash_first(scratch_pool, apply_on_props); hi; hi = apr_hash_next(hi)) { - const char *propname = svn__apr_hash_index_key(hi); + const char *propname = apr_hash_this_key(hi); svn_string_t *new_value = NULL; new_value = svn_hash_gets(resolve_from, propname); svn_hash_sets(actual_props, propname, new_value); } - SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, actual_props, - FALSE, NULL, NULL, - scratch_pool)); } + /*else the user accepted the properties as-is */ - /* Legacy behavior: Only report property conflicts as resolved when the - property reject file exists + /* This function handles conflicted_propname "" as resolving + all property conflicts... Just what we need here */ + SVN_ERR(svn_wc__conflict_skel_resolve(&resolved_all, conflicts, + db, local_abspath, + FALSE, conflicted_propname, + FALSE, + scratch_pool, scratch_pool)); - If not the UI shows the conflict as already resolved - (and in this case we just remove the in-db conflict) */ + if (!resolved_all) + { + /* Are there still property conflicts left? (or only...) */ + SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, NULL, &prop_conflicted, + NULL, db, local_abspath, conflicts, + scratch_pool, scratch_pool)); - { - svn_skel_t *work_item; + resolved_all_prop = (! prop_conflicted); + } + else + { + resolved_all_prop = TRUE; + conflicts = NULL; + } - SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, - db, local_abspath, prop_reject_file, - scratch_pool, scratch_pool)); - work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); - } + if (resolved_all_prop) + { + /* Legacy behavior: Only report property conflicts as resolved when the + property reject file exists + + If not the UI shows the conflict as already resolved + (and in this case we just remove the in-db conflict) */ + SVN_ERR(remove_artifact_file_if_exists(&work_items, did_resolve, + db, local_abspath, + prop_reject_file, + scratch_pool, scratch_pool)); + } + else + { + /* Create a new prej file, based on the remaining conflicts */ + SVN_ERR(svn_wc__wq_build_prej_install(&work_items, + db, local_abspath, + scratch_pool, scratch_pool)); + *did_resolve = TRUE; /* We resolved a property conflict */ + } + + /* This installs the updated conflict skel */ + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, actual_props, + FALSE, conflicts, work_items, + scratch_pool)); + + if (resolved_all) + { + /* Remove the whole conflict. Should probably be integrated + into the op_set_props() call */ + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, + FALSE, TRUE, FALSE, + NULL, scratch_pool)); + } - SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, TRUE, FALSE, - work_items, scratch_pool)); SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, scratch_pool)); @@ -2699,12 +2602,18 @@ resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. * * It is not an error if there is no tree conflict. + * + * If the conflict can't be resolved yet because another tree conflict is + * blocking a storage location, store the tree conflict in the RESOLVE_LATER + * hash. */ static svn_error_t * resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, svn_wc__db_t *db, const char *local_abspath, + const svn_skel_t *conflicts, svn_wc_conflict_choice_t conflict_choice, + apr_hash_t *resolve_later, svn_wc_notify_func2_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, @@ -2713,24 +2622,20 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, { svn_wc_conflict_reason_t reason; svn_wc_conflict_action_t action; - svn_skel_t *conflicts; svn_wc_operation_t operation; svn_boolean_t tree_conflicted; + const char *src_op_root_abspath; *did_resolve = FALSE; - SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, - scratch_pool, scratch_pool)); - if (!conflicts) - return SVN_NO_ERROR; - SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, &tree_conflicted, db, local_abspath, conflicts, scratch_pool, scratch_pool)); if (!tree_conflicted) return SVN_NO_ERROR; - SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL, + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, + &src_op_root_abspath, db, local_abspath, conflicts, scratch_pool, scratch_pool)); @@ -2738,6 +2643,7 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, if (operation == svn_wc_operation_update || operation == svn_wc_operation_switch) { + svn_error_t *err; if (reason == svn_wc_conflict_reason_deleted || reason == svn_wc_conflict_reason_replaced) { @@ -2745,21 +2651,88 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, { /* Break moves for any children moved out of this directory, * and leave this directory deleted. */ - SVN_ERR(svn_wc__db_resolve_break_moved_away_children( - db, local_abspath, notify_func, notify_baton, - scratch_pool)); + + if (action != svn_wc_conflict_action_delete) + { + SVN_ERR(svn_wc__db_op_break_moved_away( + db, local_abspath, src_op_root_abspath, TRUE, + notify_func, notify_baton, + scratch_pool)); + *did_resolve = TRUE; + return SVN_NO_ERROR; /* Marked resolved by function*/ + } + /* else # The move is/moves are already broken */ + + *did_resolve = TRUE; } else if (conflict_choice == svn_wc_conflict_choose_mine_conflict) { - /* Raised moved-away conflicts on any children moved out of - * this directory, and leave this directory deleted. + svn_skel_t *new_conflicts; + + /* Raise moved-away conflicts on any children moved out of + * this directory, and leave this directory as-is. + * * The newly conflicted moved-away children will be updated * if they are resolved with 'mine_conflict' as well. */ - SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away( + err = svn_wc__db_op_raise_moved_away( db, local_abspath, notify_func, notify_baton, - scratch_pool)); - *did_resolve = TRUE; + scratch_pool); + + if (err) + { + const char *dup_abspath; + + if (!resolve_later + || err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE) + return svn_error_trace(err); + + svn_error_clear(err); + dup_abspath = apr_pstrdup(apr_hash_pool_get(resolve_later), + local_abspath); + + svn_hash_sets(resolve_later, dup_abspath, dup_abspath); + + return SVN_NO_ERROR; /* Retry after other conflicts */ + } + + /* We might now have a moved-away on *this* path, let's + try to resolve that directly if that is the case */ + SVN_ERR(svn_wc__db_read_conflict(&new_conflicts, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (new_conflicts) + SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, NULL, NULL, + &tree_conflicted, + db, local_abspath, + new_conflicts, + scratch_pool, + scratch_pool)); + + if (!new_conflicts || !tree_conflicted) + { + /* TC is marked resolved by calling + svn_wc__db_resolve_delete_raise_moved_away */ + *did_resolve = TRUE; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, + &src_op_root_abspath, + db, local_abspath, + new_conflicts, + scratch_pool, + scratch_pool)); + + if (reason != svn_wc_conflict_reason_moved_away) + { + *did_resolve = TRUE; + return SVN_NO_ERROR; /* We fixed one, but... */ + } + + conflicts = new_conflicts; + /* Fall through in moved_away handling */ } else return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, @@ -2770,8 +2743,9 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, svn_dirent_local_style(local_abspath, scratch_pool)); } - else if (reason == svn_wc_conflict_reason_moved_away - && action == svn_wc_conflict_action_edit) + + if (reason == svn_wc_conflict_reason_moved_away + && action == svn_wc_conflict_action_edit) { /* After updates, we can resolve local moved-away * vs. any incoming change, either by updating the @@ -2779,12 +2753,31 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, * move (theirs-conflict). */ if (conflict_choice == svn_wc_conflict_choose_mine_conflict) { - SVN_ERR(svn_wc__db_update_moved_away_conflict_victim( - db, local_abspath, - notify_func, notify_baton, + err = svn_wc__db_update_moved_away_conflict_victim( + db, local_abspath, src_op_root_abspath, + operation, action, reason, cancel_func, cancel_baton, - scratch_pool)); - *did_resolve = TRUE; + notify_func, notify_baton, + scratch_pool); + + if (err) + { + const char *dup_abspath; + + if (!resolve_later + || err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE) + return svn_error_trace(err); + + svn_error_clear(err); + dup_abspath = apr_pstrdup(apr_hash_pool_get(resolve_later), + local_abspath); + + svn_hash_sets(resolve_later, dup_abspath, dup_abspath); + + return SVN_NO_ERROR; /* Retry after other conflicts */ + } + else + *did_resolve = TRUE; } else if (conflict_choice == svn_wc_conflict_choose_merged) { @@ -2792,14 +2785,12 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, * working copy state instead of updating the move. * Else the move would be left in an invalid state. */ - /* ### This breaks the move but leaves the conflict - ### involving the move until - ### svn_wc__db_op_mark_resolved. */ - SVN_ERR(svn_wc__db_resolve_break_moved_away(db, local_abspath, - notify_func, - notify_baton, - scratch_pool)); + SVN_ERR(svn_wc__db_op_break_moved_away(db, local_abspath, + src_op_root_abspath, TRUE, + notify_func, notify_baton, + scratch_pool)); *did_resolve = TRUE; + return SVN_NO_ERROR; /* Conflict is marked resolved */ } else return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, @@ -2810,22 +2801,57 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, svn_dirent_local_style(local_abspath, scratch_pool)); } + else if (reason == svn_wc_conflict_reason_moved_away + && action != svn_wc_conflict_action_edit) + { + /* action added is impossible, because that would imply that + something was added, but before that already moved... + (which would imply a replace) */ + SVN_ERR_ASSERT(action == svn_wc_conflict_action_delete + || action == svn_wc_conflict_action_replace); + + if (conflict_choice == svn_wc_conflict_choose_merged) + { + /* Whatever was moved is removed at its original location by the + update. That must also remove the recording of the move, so + we don't have to do anything here. */ + + *did_resolve = TRUE; + } + else if (conflict_choice == svn_wc_conflict_choose_mine_conflict) + { + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be " + "resolved to 'working' state; " + "'%s' is no longer moved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + } } - if (! *did_resolve && conflict_choice != svn_wc_conflict_choose_merged) + if (! *did_resolve) { - /* For other tree conflicts, there is no way to pick - * theirs-full or mine-full, etc. Throw an error if the - * user expects us to be smarter than we really are. */ - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, - NULL, - _("Tree conflict can only be " - "resolved to 'working' state; " - "'%s' not resolved"), - svn_dirent_local_style(local_abspath, - scratch_pool)); + if (conflict_choice != svn_wc_conflict_choose_merged) + { + /* For other tree conflicts, there is no way to pick + * theirs-full or mine-full, etc. Throw an error if the + * user expects us to be smarter than we really are. */ + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be " + "resolved to 'working' state; " + "'%s' not resolved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else + *did_resolve = TRUE; } + SVN_ERR_ASSERT(*did_resolve); + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, FALSE, TRUE, NULL, scratch_pool)); SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, @@ -2836,16 +2862,33 @@ resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, svn_error_t * svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db, const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, apr_pool_t *scratch_pool) { - svn_boolean_t ignored_result; + svn_skel_t *work_items; + svn_skel_t *conflict; - return svn_error_trace(resolve_text_conflict_on_node( - &ignored_result, - db, local_abspath, - svn_wc_conflict_choose_merged, NULL, - NULL, NULL, - scratch_pool)); + SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (!conflict) + return SVN_NO_ERROR; + + SVN_ERR(build_text_conflict_resolve_items(&work_items, NULL, + db, local_abspath, conflict, + svn_wc_conflict_choose_merged, + NULL, FALSE, NULL, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, TRUE, FALSE, FALSE, + work_items, scratch_pool)); + + return svn_error_trace(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); } svn_error_t * @@ -2854,11 +2897,20 @@ svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db, apr_pool_t *scratch_pool) { svn_boolean_t ignored_result; + svn_skel_t *conflicts; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (!conflicts) + return SVN_NO_ERROR; return svn_error_trace(resolve_prop_conflict_on_node( &ignored_result, - db, local_abspath, "", - svn_wc_conflict_choose_merged, NULL, + db, local_abspath, conflicts, "", + svn_wc_conflict_choose_merged, + NULL, NULL, NULL, NULL, scratch_pool)); } @@ -2878,8 +2930,39 @@ struct conflict_status_walker_baton void *cancel_baton; svn_wc_notify_func2_t notify_func; void *notify_baton; + svn_boolean_t resolved_one; + apr_hash_t *resolve_later; }; +/* Implements svn_wc_notify_func2_t to collect new conflicts caused by + resolving a tree conflict. */ +static void +tree_conflict_collector(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + struct conflict_status_walker_baton *cswb = baton; + + if (cswb->notify_func) + cswb->notify_func(cswb->notify_baton, notify, pool); + + if (cswb->resolve_later + && (notify->action == svn_wc_notify_tree_conflict + || notify->prop_state == svn_wc_notify_state_conflicted + || notify->content_state == svn_wc_notify_state_conflicted)) + { + if (!svn_hash_gets(cswb->resolve_later, notify->path)) + { + const char *dup_path; + + dup_path = apr_pstrdup(apr_hash_pool_get(cswb->resolve_later), + notify->path); + + svn_hash_sets(cswb->resolve_later, dup_path, dup_path); + } + } +} + /* Implements svn_wc_status4_t to walk all conflicts to resolve. */ static svn_error_t * @@ -2895,13 +2978,17 @@ conflict_status_walker(void *baton, apr_pool_t *iterpool; int i; svn_boolean_t resolved = FALSE; + svn_skel_t *conflict; if (!status->conflicted) return SVN_NO_ERROR; iterpool = svn_pool_create(scratch_pool); - SVN_ERR(svn_wc__read_conflicts(&conflicts, db, local_abspath, TRUE, + SVN_ERR(svn_wc__read_conflicts(&conflicts, &conflict, + db, local_abspath, + (cswb->conflict_func != NULL) /* tmp files */, + FALSE /* only tree conflicts */, scratch_pool, iterpool)); for (i = 0; i < conflicts->nelts; i++) @@ -2909,11 +2996,15 @@ conflict_status_walker(void *baton, const svn_wc_conflict_description2_t *cd; svn_boolean_t did_resolve; svn_wc_conflict_choice_t my_choice = cswb->conflict_choice; - const char *merged_file = NULL; + svn_wc_conflict_result_t *result = NULL; + svn_skel_t *work_items; cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *); - if ((cd->kind == svn_wc_conflict_kind_property && !cswb->resolve_prop) + if ((cd->kind == svn_wc_conflict_kind_property + && (!cswb->resolve_prop + || (*cswb->resolve_prop != '\0' + && strcmp(cswb->resolve_prop, cd->property_name) != 0))) || (cd->kind == svn_wc_conflict_kind_text && !cswb->resolve_text) || (cd->kind == svn_wc_conflict_kind_tree && !cswb->resolve_tree)) { @@ -2924,8 +3015,6 @@ conflict_status_walker(void *baton, if (my_choice == svn_wc_conflict_choose_unspecified) { - svn_wc_conflict_result_t *result; - if (!cswb->conflict_func) return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("No conflict-callback and no " @@ -2935,8 +3024,6 @@ conflict_status_walker(void *baton, iterpool, iterpool)); my_choice = result->choice; - merged_file = result->merged_file; - /* ### Bug: ignores result->save_merged */ } @@ -2946,54 +3033,57 @@ conflict_status_walker(void *baton, switch (cd->kind) { case svn_wc_conflict_kind_tree: - if (!cswb->resolve_tree) - break; SVN_ERR(resolve_tree_conflict_on_node(&did_resolve, db, - local_abspath, + local_abspath, conflict, my_choice, - cswb->notify_func, - cswb->notify_baton, + cswb->resolve_later, + tree_conflict_collector, + cswb, cswb->cancel_func, cswb->cancel_baton, iterpool)); - resolved = TRUE; + if (did_resolve) + resolved = TRUE; break; case svn_wc_conflict_kind_text: - if (!cswb->resolve_text) - break; - - SVN_ERR(resolve_text_conflict_on_node(&did_resolve, - db, - local_abspath, - my_choice, - merged_file, - cswb->cancel_func, - cswb->cancel_baton, - iterpool)); - - if (did_resolve) - resolved = TRUE; + SVN_ERR(build_text_conflict_resolve_items( + &work_items, + &resolved, + db, local_abspath, conflict, + my_choice, + result ? result->merged_file + : NULL, + result ? result->save_merged + : FALSE, + NULL /* merge_options */, + cswb->cancel_func, + cswb->cancel_baton, + iterpool, iterpool)); + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, + TRUE, FALSE, FALSE, + work_items, iterpool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, + cswb->cancel_func, cswb->cancel_baton, + iterpool)); break; case svn_wc_conflict_kind_property: - if (!cswb->resolve_prop) - break; - - if (*cswb->resolve_prop != '\0' && - strcmp(cswb->resolve_prop, cd->property_name) != 0) - { - break; /* This is not the property we want to resolve. */ - } - SVN_ERR(resolve_prop_conflict_on_node(&did_resolve, db, local_abspath, + conflict, cd->property_name, my_choice, - merged_file, + result + ? result->merged_file + : NULL, + result + ? result->merged_value + : NULL, cswb->cancel_func, cswb->cancel_baton, iterpool)); @@ -3016,6 +3106,9 @@ conflict_status_walker(void *baton, iterpool), iterpool); + if (resolved) + cswb->resolved_one = TRUE; + svn_pool_destroy(iterpool); return SVN_NO_ERROR; @@ -3040,13 +3133,8 @@ svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx, svn_node_kind_t kind; svn_boolean_t conflicted; struct conflict_status_walker_baton cswb; - - /* ### the underlying code does NOT support resolving individual - ### properties. bail out if the caller tries it. */ - if (resolve_prop != NULL && *resolve_prop != '\0') - return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, - U_("Resolving a single property is not (yet) " - "supported.")); + apr_pool_t *iterpool = NULL; + svn_error_t *err; /* ### Just a versioned check? */ /* Conflicted is set to allow invoking on actual only nodes */ @@ -3079,6 +3167,11 @@ svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx, cswb.notify_func = notify_func; cswb.notify_baton = notify_baton; + cswb.resolved_one = FALSE; + cswb.resolve_later = (depth != svn_depth_empty) + ? apr_hash_make(scratch_pool) + : NULL; + if (notify_func) notify_func(notify_baton, svn_wc_create_notify(local_abspath, @@ -3086,16 +3179,103 @@ svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx, scratch_pool), scratch_pool); - SVN_ERR(svn_wc_walk_status(wc_ctx, - local_abspath, - depth, - FALSE /* get_all */, - FALSE /* no_ignore */, - TRUE /* ignore_text_mods */, - NULL /* ignore_patterns */, - conflict_status_walker, &cswb, - cancel_func, cancel_baton, - scratch_pool)); + err = svn_wc_walk_status(wc_ctx, + local_abspath, + depth, + FALSE /* get_all */, + FALSE /* no_ignore */, + TRUE /* ignore_text_mods */, + NULL /* ignore_patterns */, + conflict_status_walker, &cswb, + cancel_func, cancel_baton, + scratch_pool); + + /* If we got new tree conflicts (or delayed conflicts) during the initial + walk, we now walk them one by one as closure. */ + while (!err && cswb.resolve_later && apr_hash_count(cswb.resolve_later)) + { + apr_hash_index_t *hi; + svn_wc_status3_t *status = NULL; + const char *tc_abspath = NULL; + + if (iterpool) + svn_pool_clear(iterpool); + else + iterpool = svn_pool_create(scratch_pool); + + hi = apr_hash_first(scratch_pool, cswb.resolve_later); + cswb.resolve_later = apr_hash_make(scratch_pool); + cswb.resolved_one = FALSE; + + for (; hi && !err; hi = apr_hash_next(hi)) + { + const char *relpath; + svn_pool_clear(iterpool); + + tc_abspath = apr_hash_this_key(hi); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + relpath = svn_dirent_skip_ancestor(local_abspath, + tc_abspath); + + if (!relpath + || (depth >= svn_depth_empty + && depth < svn_depth_infinity + && strchr(relpath, '/'))) + { + continue; + } + + SVN_ERR(svn_wc_status3(&status, wc_ctx, tc_abspath, + iterpool, iterpool)); + + if (depth == svn_depth_files + && status->kind == svn_node_dir) + continue; + + err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath, + status, scratch_pool)); + } + + /* None of the remaining conflicts got resolved, and non did provide + an error... + + We can fix that if we disable the 'resolve_later' option... + */ + if (!cswb.resolved_one && !err && tc_abspath + && apr_hash_count(cswb.resolve_later)) + { + /* Run the last resolve operation again. We still have status + and tc_abspath for that one. */ + + cswb.resolve_later = NULL; /* Produce proper error! */ + + /* Recreate the error */ + err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath, + status, scratch_pool)); + + SVN_ERR_ASSERT(err != NULL); + + err = svn_error_createf( + SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, + _("Unable to resolve pending conflict on '%s'"), + svn_dirent_local_style(tc_abspath, scratch_pool)); + break; + } + } + + if (iterpool) + svn_pool_destroy(iterpool); + + if (err && err->apr_err != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE) + err = svn_error_createf( + SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err, + _("Unable to resolve conflicts on '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + SVN_ERR(err); if (notify_func) notify_func(notify_baton, @@ -3139,7 +3319,7 @@ svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice, { svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result)); result->choice = choice; - result->merged_file = merged_file; + result->merged_file = apr_pstrdup(pool, merged_file); result->save_merged = FALSE; /* If we add more fields to svn_wc_conflict_result_t, add them here. */ |