This branch implements the 'invoke-diff3-cmd' feature and is located at: https://svn.apache.org/repos/asf/subversion/branches/invoke-diff3-feature/ It is a continuation of the invoke-diff-cmd-feature branch r1541740, which is located here: https://svn.apache.org/repos/asf/subversion/branches/invoke-diff-cmd-feature/ It is a feature branch, receiving regular sync merges from /trunk, and expected to be reintegrated back thereto. See: http://subversion.tigris.org/issues/show_bug.cgi?id=2044 for the original motivation for this project. Index: ====== 1. Introduction 2. What --invoke-diff3-cmd provides 3. Structure of the feature API components UI components 4. Changes to the existing code structure 5. Known Problems 6. Tests Automatic tests Manual tests passed Working copy functionality test 7. TODO 8. Log messages 9. DIFF from invoke-diff-cmd branch 1541740 1. Introduction: ================ --invoke-diff3-cmd allows command line, config file and interactive selection of an external diff3 program. Currently this capability is provided by user written shell scripts which are passed as the merge program via the svn config file, and on the command line via the --diff3_cmd and the -x extensions. 2. What --invoke-diff3-cmd provides =================================== Users can type 'free-style' command lines for their selected diff3 program, define a default command in the config file and also, interactively select a diff3 program during the merge conflict dialogue. 3. Structure of the feature: ============================ API components -------------- * svn_merge5() has been deprecated to svn_merge6(). UI components ------------- --invoke-diff-cmd and its user interface components for the command line have been installed everywhere where --diff-cmd is available. Users can specify the following mutually exclusive options: --invoke-merge-cmd, --diff3-cmd , and also define what diff3 program to use via the ./svn/config file, using the new config file parameter "invoke-merge-cmd".. The selection first defaults to the config file entry (if defined) which in turn is overridden when one of --invoke-diff3-cmd, --diff-cmd are invoked. The interactive merge section has 2 new options: Interactive selection of the config file define, and an input option for a invoke-diff3-cmd command style instruction. 4. Changes to the existing code structure: ========================================== None. The existing --diff3-cmd has been in use for years and is best left untouched, since many users will not require the extra services offered by the --invoke-diff3-cmd feature, and any errors could cause many users a lot of problems. Moreover, the nature of --invoke-diff3-cmd and --diff-cmd are quite different; --diff-3 is tailored for GNU diff3 and carefully checks the user's input of switches, whereas --invoke-diff3-cmd is a free-style-anything-goes-including-shooting-your-foot creation. Known Problems ============== The error trace back in subversion/svn/conflict-callbacks.c is defective in general since the response to a user error is 'traced call' instead of the actual error being displayed. I've kept the current shape to match the 'l' option, since I think that is a seperate bug outside the scope of this patch. see line 902 ('l' option) and line 940 ('i' option) Merge of 'subversion/libsvn_client/diff.c' aborted. Select: (p) postpone, (df) show diff, (e) edit file, (m) merge, (i) interactive invoke-diff3-cmd selection, (mc) my side of conflict, (tc) their side of conflict, (s) show all options: l l traced call Select: (p) postpone, (df) show diff, (e) edit file, (m) merge, (i) interactive invoke-diff3-cmd selection, (mc) my side of conflict, (tc) their side of conflict, (s) show all options: i i Enter the invoke-diff3-cmd: flobble flobble traced call Select: (p) postpone, (df) show diff, (e) edit file, (m) merge, (i) interactive invoke-diff3-cmd selection, (mc) my side of conflict, (tc) their side of conflict, (s) show all options: 6. Tests ======== Automatic tests --------------- I could not find a pre-existing test for the --diff3-cmd option and so I omitted writing an automated test for now. Manual tests passed ------------------- * config file selection '--invoke-diff3-cmd' invocation works. overrides interactive invoke-diff3-cmd selection. * --invoke-diff3-cmd commandline invocation works. overrides config file selection. * interactive --invoke-diff3-cmd commandline invocation works. * --diff3-cmd and --invoke-diff3-cmd mututally exclusive. Working copy functionality test ------------------------------- Create a suitable conflict to test the UI part of the feature: mkdir conflict svn co -r r1526439 https://svn.apache.org/repos/asf/subversion/trunk/ trunk svn co -r r1502389 https://svn.apache.org/repos/asf/subversion/branches//invoke-diff-cmd-feature branch cd branch svn /home/g/conflict/trunk revert -R . The above produces 2 conflicts using the native svn merge command, and 30 conflicts when kdiff3 is used. $ kdiff3 --version Qt: 4.8.4 KDE Development Platform: 4.10.5 kdiff3: 0.9.97 (32 bit) 7. TODO: ======== * Dear Reviwer: please ensure I got the left and right merges the correct way round. I think I have, but given that the left thumb points to where the right hand is... %-) * Decide which smiley character to choose: ./subversion/svn/conflict-callbacks.c:122 8. Log messages =============== * BRANCH-README (*) Description of Branch. * subversion/include/private/svn_wc_private.h (svn_wc__get_file_external_editor, (svn_wc__get_update_editor, (svn_wc__get_switch_editor): New parameter: const char *invoke_diff3_cmd. * subversion/include/svn_config.h (): New definition: SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD. * subversion/include/svn_error_codes.h (command-line client errors): New definition: SVN_ERR_CL_NO_EXTERNAL_DIFF3_TOOL. (SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS): Adjust numbering in order to retain a logical grouping. * subversion/include/svn_io.h (svn_io_run_invoke_diff3): New function. * subversion/include/svn_wc.h (svn_wc_merge6): New function. (svn_wc_merge5): Deprecate. * subversion/libsvn_client/externals.c (switch_file_external): New variable: const char *invoke_diff3_cmd. Initialize diff3_cmd to NULL. Add precedence logic and read the config file to populate invoke_diff3_cmd. Add invoke_diff3_cmd in call to svn_wc__get_file_external_editor(). * subversion/libsvn_client/merge.c (merge_cmd_baton_t): New member *invoke_diff3_cmd. Adjust comment. (merge_file_changed): Replace call to svn_wc_merge5 with call to svn_wc_merge6. (do_merge): New variable: const char *invoke_diff3_cmd. Initialize diff3_cmd to NULL. Adjust comment. Add precedence logic and read the config file to populate invoke_diff3_cmd. Add invoke_diff3_cmd to merge_baton. * subversion/libsvn_client/switch.c (switch_internal): New variable: const char *invoke_diff3_cmd. Add precedence logic and read the config file to populate invoke_diff3_cmd. Add invoke_diff3_cmd in call to svn_wc__get_switch_editor(). * subversion/libsvn_client/update.c (update_internal): Initialize diff3_cmd to NULL. New variable: *invoke_diff3_cmd. Adjust comment, add precedence logic and read the config file to populate invoke_diff3_cmd. Add invoke_diff3_cmd in call to svn_wc__get_update_editor(). * subversion/libsvn_subr/config_file.c (svn_config_ensure): Add entry for invoke-diff3-cmd. * subversion/libsvn_subr/io.c (svn_io_run_invoke_diff3): New function. * subversion/libsvn_wc/deprecated.c (svn_wc_get_update_editor4): Add invoke-diff3-cmd as NULL to call to svn_wc__get_update_editor(). (svn_wc_get_switch_editor4): Add invoke-diff3-cmd as NULL to call to svn_wc__get_switch_editor(). (svn_wc_merge5): New function. * subversion/libsvn_wc/externals.c (struct edit_baton): Rename diff3cmd to diff3_cmd. New member variable: const char *invoke_diff3_cmd. (close_file): Rename diff3cmd to diff3_cmd and add invoke_diff3_cmd in call to svn_wc__perform_file_merge(). (svn_wc__get_file_external_editor): New parameter: const char *invoke_diff3_cmd. Rename diff3cmd to diff3_cmd in eb assignment and assign invoke_diff3_cmd to struct edit_baton eb. * subversion/libsvn_wc/merge.c (merge_target_t): Adjust comment for old_actual_props. New member variable: invoke_diff3_cmd. (do_text_merge_external): New parameter: const char invoke_diff3_cmd. Add if condition to route call to newly added svn_io_run_invoke_diff3(). Add clarifying braces around A = (B == C) type assignment. (merge_text_file): Adjust if condition to route call to do_text_merge_external() and add invoke_diff3_cmd to call to do_text_merge_external(). (svn_wc__internal_merge): New parameter: const char *invoke_diff3_cmd. Assign invoke_diff3_cmd to mt. Add invoke_diff3_cmd as a conditional variable in to call to detranslate_wc_file(). (svn_wc_merge6): Declare new API. * subversion/libsvn_wc/update_editor.c (struct edit_baton): New member const char *invoke_diff3_cmd. (svn_wc__perform_file_merge): New parameter: const char *invoke_diff3_cmd. Add invoke_diff3_cmd to svn_wc__internal_merge(). (merge_file): Add invoke_diff3_cmd to svn_wc__internal_merge(). (make_editor): New parameter: const char *invoke_diff3_cmd. Assign invoke_diff3_cmd to eb. (svn_wc__get_update_editor, svn_wc__get_switch_editor): New parameter: const char *invoke_diff3_cmd. Add invoke_diff3_cmd to call to make_editor(). * subversion/libsvn_wc/wc.h (svn_wc__internal_merge): Adjust function comment. New parameter: const char *invoke_diff3_cmd. Add newline to conform to general formatting in this file. (svn_wc__perform_file_merge): New parameter: const char *invoke_diff3_cmd. * subversion/libsvn_wc/wc_db_update_move.c (update_working_file): Add invoke_diff3_cmd in call to svn_wc__perform_file_merge() as NULL. Adjust other comments for neatness. * subversion/svn/cl.h (svn_cl__accept_t): New commented member: svn_cl__accept_invoke_diff3_config. (): New definition: SVN_CL__ACCEPT_INVOKE_DIFF3_CONFIG. (svn_cl__opt_state_t): New member const char* invoke_diff3_cmd. (svn_cl__invoke_diff3_cmd_externally): New function. * subversion/svn/conflict-callbacks.c (svn_cl__accept_from_word): Add SVN_CL__ACCEPT_INVOKE_DIFF3_CONFIG to if condition returns svn_cl__accept_launch. (invoke_diff3_resolver): New routing function that calls svn_cl__invoke_diff3_cmd_externally(). (text_conflict_options): Add "3f" option for invoke-diff3-cmd tool selection and add 'i' option for interactive invoke-diff3-cmd input. (handle_text_conflict): Add interactive selection code for invoke-diff3-cmd input ("i" section) and invoke-diff3-cmd tool ("3f" section) to if condition. Add '3f' and 'i' to next_option. Adjust general indentation(2 entries). (conflict_func_interactive): Add cases for "i" and "3f" selection. Adjust general indentation. Add case 'svn_cl__accept_invoke_diff3_config'. * subversion/svn/svn.c (svn_cl__longopt_t): New member 'opt_diff3_cmd'. (svn_cl__options): Add 'invoke-diff3-cmd' entry plus help text. (svn_cl__cmd_table,"cleanup","merge","switch","update"): Add opt_invoke_diff3_cmd. (sub_main): Add case opt_invoke_diff3_cmd. Ensure murutal exclusiveness of diff3-cmd and invoke-diff3-cmd. Add call to svn_config_set(). Add error check for svn_cl__accept_invoke_diff3_config. * subversion/svn/util.c (): Include svn_io_private.h (svn_cl__invoke_diff3_cmd_externally): New function. * subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout (): Update help output. 9. DIFF from invoke-diff-cmd branch 1541740: ============================================ Index: subversion/include/private/svn_wc_private.h =================================================================== --- subversion/include/private/svn_wc_private.h (revision 1542103) +++ subversion/include/private/svn_wc_private.h (working copy) @@ -77,6 +77,7 @@ svn_wc__get_file_external_editor(const svn_delta_e apr_array_header_t *iprops, svn_boolean_t use_commit_times, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *preserved_exts, const char *record_ancestor_abspath, const char *recorded_url, @@ -1507,6 +1508,7 @@ svn_wc__get_update_editor(const svn_delta_editor_t svn_boolean_t server_performs_filtering, svn_boolean_t clean_checkout, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *preserved_exts, svn_wc_dirents_func_t fetch_dirents_func, void *fetch_dirents_baton, @@ -1549,6 +1551,7 @@ svn_wc__get_switch_editor(const svn_delta_editor_t svn_boolean_t allow_unver_obstructions, svn_boolean_t server_performs_filtering, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *preserved_exts, svn_wc_dirents_func_t fetch_dirents_func, void *fetch_dirents_baton, Index: subversion/include/svn_config.h =================================================================== --- subversion/include/svn_config.h (revision 1542103) +++ subversion/include/svn_config.h (working copy) @@ -116,6 +116,8 @@ typedef struct svn_config_t svn_config_t; #define SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG "diff3-has-program-arg" /** @since New in 1.9. */ #define SVN_CONFIG_OPTION_INVOKE_DIFF_CMD "invoke-diff-cmd" +/** @since New in 1.9. */ +#define SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD "invoke-diff3-cmd" #define SVN_CONFIG_OPTION_MERGE_TOOL_CMD "merge-tool-cmd" #define SVN_CONFIG_SECTION_MISCELLANY "miscellany" #define SVN_CONFIG_OPTION_GLOBAL_IGNORES "global-ignores" Index: subversion/include/svn_error_codes.h =================================================================== --- subversion/include/svn_error_codes.h (revision 1542103) +++ subversion/include/svn_error_codes.h (working copy) @@ -1451,8 +1451,13 @@ SVN_ERROR_START SVN_ERR_CL_CATEGORY_START + 10, "No external merge tool available") + /** @since New in 1.9. */ + SVN_ERRDEF(SVN_ERR_CL_NO_EXTERNAL_DIFF3_TOOL, + SVN_ERR_CL_CATEGORY_START + 11, + "No external invoke-diff3 tool available") + SVN_ERRDEF(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, - SVN_ERR_CL_CATEGORY_START + 11, + SVN_ERR_CL_CATEGORY_START + 12, "Failed processing one or more externals definitions") /* ra_svn errors */ Index: subversion/include/svn_io.h =================================================================== --- subversion/include/svn_io.h (revision 1542103) +++ subversion/include/svn_io.h (working copy) @@ -2378,6 +2378,25 @@ svn_io_run_external_diff(const char *dir, const char *external_diff_cmd, apr_pool_t *scratch_pool); +/** Run the external merge command defined by the invoke-diff3-cmd + * option. + * + * @since New in 1.9. + */ +svn_error_t * +svn_io_run_invoke_diff3(int *exitcode, + const char *dir, + const char *mine, + const char *older, + const char *yours, + const char *mine_label, + const char *older_label, + const char *yours_label, + apr_file_t *merged, + const char *diff3_cmd, + apr_pool_t *pool); + + #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/include/svn_wc.h =================================================================== --- subversion/include/svn_wc.h (revision 1542103) +++ subversion/include/svn_wc.h (working copy) @@ -7011,6 +7011,35 @@ typedef enum svn_wc_merge_outcome_t * @since New in 1.8. */ svn_error_t * +svn_wc_merge6(enum svn_wc_merge_outcome_t *merge_content_outcome, + enum svn_wc_notify_state_t *merge_props_state, + svn_wc_context_t *wc_ctx, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_boolean_t dry_run, + const char *diff3_cmd, + const char *invoke_diff3_cmd, + const apr_array_header_t *merge_options, + apr_hash_t *original_props, + const apr_array_header_t *prop_diff, + 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); + +/** Similar to svn_wc_merge6() but with @a invoke_diff3_cmd. + * + * @since New in 1.9. + */ +SVN_DEPRECATED +svn_error_t * svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome, enum svn_wc_notify_state_t *merge_props_state, svn_wc_context_t *wc_ctx, Index: subversion/libsvn_client/externals.c =================================================================== --- subversion/libsvn_client/externals.c (revision 1542103) +++ subversion/libsvn_client/externals.c (working copy) @@ -364,7 +364,8 @@ switch_file_external(const char *local_abspath, ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; svn_boolean_t use_commit_times; - const char *diff3_cmd; + const char *diff3_cmd = NULL; + const char *invoke_diff3_cmd = NULL; const char *preserved_exts_str; const apr_array_header_t *preserved_exts; svn_node_kind_t kind, external_kind; @@ -376,13 +377,23 @@ switch_file_external(const char *local_abspath, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); - /* Get the external diff3, if any. */ + /* Get the external *_diff3_cmd, if any. + Precedence: If there is no invoke_diff3_cmd on the cmd line, + check if there is a diff3-cmd in the config file. If there is, + do not check invoke_diff3_cmd in the config file.*/ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); - + else + { + svn_config_get(cfg, &invoke_diff3_cmd, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD, NULL); + if (invoke_diff3_cmd != NULL) + SVN_ERR(svn_path_cstring_to_utf8(&invoke_diff3_cmd, + invoke_diff3_cmd, scratch_pool)); + } /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, @@ -483,7 +494,8 @@ switch_file_external(const char *local_abspath, switch_loc->repos_uuid, inherited_props, use_commit_times, - diff3_cmd, preserved_exts, + diff3_cmd, invoke_diff3_cmd, + preserved_exts, def_dir_abspath, url, peg_revision, revision, ctx->conflict_func2, Index: subversion/libsvn_client/merge.c =================================================================== --- subversion/libsvn_client/merge.c (revision 1542103) +++ subversion/libsvn_client/merge.c (working copy) @@ -316,10 +316,13 @@ typedef struct merge_cmd_baton_t { /* A list of tree conflict victim absolute paths which may be NULL. */ apr_hash_t *tree_conflicted_abspaths; - /* The diff3_cmd in ctx->config, if any, else null. We could just - extract this as needed, but since more than one caller uses it, - we just set it up when this baton is created. */ + /* The diff3_cmd and invoke_diff3_cmd in ctx->config, if any, else + null. We could just extract this as needed, but since more than + one caller uses it, we just set it up when this baton is + created. */ const char *diff3_cmd; + const char *invoke_diff3_cmd; + const apr_array_header_t *merge_options; /* RA sessions used throughout a merge operation. Opened/re-parented @@ -2040,11 +2043,13 @@ merge_file_changed(const char *relpath, /* Do property merge and text merge in one step so that keyword expansion takes into account the new property values. */ - SVN_ERR(svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx, + SVN_ERR(svn_wc_merge6(&content_outcome, &property_state, ctx->wc_ctx, left_file, right_file, local_abspath, left_label, right_label, target_label, left, right, - merge_b->dry_run, merge_b->diff3_cmd, + merge_b->dry_run, + merge_b->diff3_cmd, + merge_b->invoke_diff3_cmd, merge_b->merge_options, left_props, prop_changes, NULL, NULL, @@ -9667,7 +9672,8 @@ do_merge(apr_hash_t **modified_subtrees, { merge_cmd_baton_t merge_cmd_baton = { 0 }; svn_config_t *cfg; - const char *diff3_cmd; + const char *diff3_cmd = NULL; + const char *invoke_diff3_cmd = NULL; int i; svn_boolean_t checked_mergeinfo_capability = FALSE; svn_ra_session_t *ra_session1 = NULL, *ra_session2 = NULL; @@ -9718,7 +9724,11 @@ do_merge(apr_hash_t **modified_subtrees, if (depth == svn_depth_unknown) depth = svn_depth_infinity; - /* Set up the diff3 command, so various callers don't have to. */ + /* Get the external *_diff3_cmd, if any. + Precedence: If there is no invoke_diff3_cmd on the cmd line, + check if there is a diff3-cmd in the config file. If there is, + do not check invoke_diff3_cmd in the config file.*/ + cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; @@ -9727,6 +9737,14 @@ do_merge(apr_hash_t **modified_subtrees, if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); + else + { + svn_config_get(cfg, &invoke_diff3_cmd, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD, NULL); + if (invoke_diff3_cmd != NULL) + SVN_ERR(svn_path_cstring_to_utf8(&invoke_diff3_cmd, + invoke_diff3_cmd, scratch_pool)); + } /* Build the merge context baton (or at least the parts of it that don't need to be reset for each merge source). */ @@ -9743,6 +9761,7 @@ do_merge(apr_hash_t **modified_subtrees, merge_cmd_baton.pool = iterpool; merge_cmd_baton.merge_options = merge_options; merge_cmd_baton.diff3_cmd = diff3_cmd; + merge_cmd_baton.invoke_diff3_cmd = invoke_diff3_cmd; merge_cmd_baton.use_sleep = use_sleep; /* Do we already know the specific subtrees with mergeinfo we want Index: subversion/libsvn_client/switch.c =================================================================== --- subversion/libsvn_client/switch.c (revision 1542103) +++ subversion/libsvn_client/switch.c (working copy) @@ -106,6 +106,7 @@ switch_internal(svn_revnum_t *result_rev, svn_ra_session_t *ra_session; svn_revnum_t revnum; const char *diff3_cmd; + const char *invoke_diff3_cmd; apr_hash_t *wcroot_iprops; apr_array_header_t *inherited_props; svn_boolean_t use_commit_times; @@ -134,7 +135,18 @@ switch_internal(svn_revnum_t *result_rev, if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); + else + /* Get the external invoke_diff3_cmd, if any. */ + svn_config_get(cfg, &invoke_diff3_cmd, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD, NULL); + if (diff3_cmd != NULL) + SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); + else + if (invoke_diff3_cmd != NULL) + SVN_ERR(svn_path_cstring_to_utf8(&invoke_diff3_cmd, invoke_diff3_cmd, pool)); + + /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, @@ -312,7 +324,8 @@ switch_internal(svn_revnum_t *result_rev, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, server_supports_depth, - diff3_cmd, preserved_exts, + diff3_cmd, invoke_diff3_cmd, + preserved_exts, svn_client__dirent_fetcher, &dfb, conflicted_paths ? record_conflict : NULL, conflicted_paths, Index: subversion/libsvn_client/update.c =================================================================== --- subversion/libsvn_client/update.c (revision 1542103) +++ subversion/libsvn_client/update.c (working copy) @@ -226,7 +226,8 @@ update_internal(svn_revnum_t *result_rev, svn_revnum_t revnum; svn_boolean_t use_commit_times; svn_boolean_t clean_checkout = FALSE; - const char *diff3_cmd; + const char *diff3_cmd = NULL; + const char *invoke_diff3_cmd = NULL; apr_hash_t *wcroot_iprops; svn_opt_revision_t opt_rev; svn_ra_session_t *ra_session; @@ -332,13 +333,24 @@ update_internal(svn_revnum_t *result_rev, /* check whether the "clean c/o" optimization is applicable */ SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool)); - /* Get the external diff3, if any. */ + /* Get the external *_diff3_cmd, if any. + Precedence: If there is no invoke_diff3_cmd on the cmd line, + check if there is a diff3-cmd in the config file. If there is, + do not check invoke_diff3_cmd in the config file.*/ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); - + else + { + svn_config_get(cfg, &invoke_diff3_cmd, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD, NULL); + if (invoke_diff3_cmd != NULL) + SVN_ERR(svn_path_cstring_to_utf8(&invoke_diff3_cmd, + invoke_diff3_cmd, pool)); + } + /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, @@ -429,7 +441,8 @@ update_internal(svn_revnum_t *result_rev, adds_as_modification, server_supports_depth, clean_checkout, - diff3_cmd, preserved_exts, + diff3_cmd, invoke_diff3_cmd, + preserved_exts, svn_client__dirent_fetcher, &dfb, conflicted_paths ? record_conflict : NULL, conflicted_paths, Index: subversion/libsvn_subr/config_file.c =================================================================== --- subversion/libsvn_subr/config_file.c (revision 1542103) +++ subversion/libsvn_subr/config_file.c (working copy) @@ -1192,6 +1192,11 @@ svn_config_ensure(const char *config_dir, apr_pool "### This will override the compile-time default, which is to use" NL "### Subversion's internal diff implementation." NL "# invoke-diff-cmd = (see svn help diff for examples)" NL + "### Set invoke-diff3-cmd to the absolute path of your 'diff'" NL + "### program." NL + "### This will override the compile-time default, which is to use" NL + "### Subversion's internal merge implementation." NL + "# invoke-diff3-cmd = (see svn help merge for examples)" NL "### Set merge-tool-cmd to the command used to invoke your external" NL "### merging tool of choice. Subversion will pass 5 arguments to" NL "### the specified command: base theirs mine merged wcfile" NL Index: subversion/libsvn_subr/io.c =================================================================== --- subversion/libsvn_subr/io.c (revision 1542103) +++ subversion/libsvn_subr/io.c (working copy) @@ -3371,7 +3371,60 @@ svn_io_run_diff3_3(int *exitcode, return SVN_NO_ERROR; } +svn_error_t * +svn_io_run_invoke_diff3(int *exitcode, + const char *dir, + const char *mine, + const char *older, + const char *yours, + const char *mine_label, + const char *older_label, + const char *yours_label, + apr_file_t *merged, + const char *invoke_diff3_cmd, + apr_pool_t *pool) +{ + const char ** cmd; + + apr_pool_t *scratch_pool = svn_pool_create(pool); + + if (0 == strlen(invoke_diff3_cmd)) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, NULL); + + cmd = svn_io__create_custom_diff_cmd(mine_label, yours_label, older_label, + mine, yours, older, + invoke_diff3_cmd, scratch_pool); + + SVN_ERR(svn_io_run_cmd(dir, cmd[0], cmd, exitcode, NULL, TRUE, + NULL, merged, NULL, scratch_pool)); + + + /* According to the diff3 docs, a '0' means the merge was clean, and + '1' means conflict markers were found. Anything else is real + error. */ + if ((*exitcode != 0) && (*exitcode != 1)) + { + + int i; + const char *failed_command = ""; + + for (i = 0; cmd[i]; ++i) + failed_command = apr_pstrcat(pool, failed_command, + cmd[i], " ", (char*) NULL); + svn_pool_destroy(scratch_pool); + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("'%s' was expanded to '%s' and returned %d"), + invoke_diff3_cmd, + failed_command, + *exitcode); + } + else + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + /* Canonicalize a string for hashing. Modifies KEY in place. */ static APR_INLINE char * fileext_tolower(char *key) Index: subversion/libsvn_wc/deprecated.c =================================================================== --- subversion/libsvn_wc/deprecated.c (revision 1542103) +++ subversion/libsvn_wc/deprecated.c (working copy) @@ -3492,6 +3492,7 @@ svn_wc_get_update_editor4(const svn_delta_editor_t server_performs_filtering, clean_checkout, diff3_cmd, + NULL, preserved_exts, fetch_dirents_func, fetch_dirents_baton, conflict_func, conflict_baton, @@ -3676,6 +3677,7 @@ svn_wc_get_switch_editor4(const svn_delta_editor_t allow_unver_obstructions, server_performs_filtering, diff3_cmd, + NULL, preserved_exts, fetch_dirents_func, fetch_dirents_baton, conflict_func, conflict_baton, @@ -4402,6 +4404,53 @@ svn_wc_copy(const char *src_path, /*** From merge.c ***/ svn_error_t * +svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome, + enum svn_wc_notify_state_t *merge_props_outcome, + svn_wc_context_t *wc_ctx, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + apr_hash_t *original_props, + const apr_array_header_t *prop_diff, + 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) +{ + return svn_wc_merge6(merge_content_outcome, + merge_props_outcome, + wc_ctx, + left_abspath, + right_abspath, + target_abspath, + left_label, + right_label, + target_label, + left_version, + right_version, + dry_run, + diff3_cmd, + NULL, + merge_options, + original_props, + prop_diff, + conflict_func, + conflict_baton, + cancel_func, + cancel_baton, + scratch_pool); +} + +svn_error_t * svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome, svn_wc_context_t *wc_ctx, const char *left_abspath, @@ -4816,3 +4865,4 @@ svn_wc__conflict_description2_dup(const svn_wc_con return new_conflict; } + Index: subversion/libsvn_wc/externals.c =================================================================== --- subversion/libsvn_wc/externals.c (revision 1542103) +++ subversion/libsvn_wc/externals.c (working copy) @@ -402,7 +402,8 @@ struct edit_baton /* Information from the caller */ svn_boolean_t use_commit_times; const apr_array_header_t *ext_patterns; - const char *diff3cmd; + const char *diff3_cmd; + const char *invoke_diff3_cmd; const char *url; const char *repos_root_url; @@ -813,7 +814,8 @@ close_file(void *file_baton, eb->original_revision, *eb->target_revision, eb->propchanges, - eb->diff3cmd, + eb->diff3_cmd, + eb->invoke_diff3_cmd, eb->cancel_func, eb->cancel_baton, pool, pool)); @@ -984,6 +986,7 @@ svn_wc__get_file_external_editor(const svn_delta_e apr_array_header_t *iprops, svn_boolean_t use_commit_times, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *preserved_exts, const char *record_ancestor_abspath, const char *recorded_url, @@ -1021,7 +1024,8 @@ svn_wc__get_file_external_editor(const svn_delta_e eb->use_commit_times = use_commit_times; eb->ext_patterns = preserved_exts; - eb->diff3cmd = diff3_cmd; + eb->diff3_cmd = diff3_cmd; + eb->invoke_diff3_cmd = invoke_diff3_cmd; eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath); eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url, Index: subversion/libsvn_wc/merge.c =================================================================== --- subversion/libsvn_wc/merge.c (revision 1542103) +++ subversion/libsvn_wc/merge.c (working copy) @@ -45,11 +45,12 @@ typedef struct merge_target_t const char *local_abspath; /* The absolute path to target */ const char *wri_abspath; /* The working copy of target */ - apr_hash_t *old_actual_props; /* The set of actual properties + apr_hash_t *old_actual_props; /* The set of actual properties before merging */ const apr_array_header_t *prop_diff; /* The property changes */ const char *diff3_cmd; /* The diff3 command and options */ + const char *invoke_diff3_cmd; /* The invoke_diff3_cmd command */ const apr_array_header_t *merge_options; } merge_target_t; @@ -437,6 +438,7 @@ static svn_error_t * do_text_merge_external(svn_boolean_t *contains_conflicts, apr_file_t *result_f, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *merge_options, const char *detranslated_target, const char *left_abspath, @@ -448,17 +450,26 @@ do_text_merge_external(svn_boolean_t *contains_con { int exit_code; - SVN_ERR(svn_io_run_diff3_3(&exit_code, ".", - detranslated_target, left_abspath, right_abspath, - target_label, left_label, right_label, - result_f, diff3_cmd, - merge_options, scratch_pool)); + if (diff3_cmd) + SVN_ERR(svn_io_run_diff3_3(&exit_code, ".", + detranslated_target, left_abspath, right_abspath, + target_label, left_label, right_label, + result_f, diff3_cmd, + merge_options, scratch_pool)); + else + SVN_ERR(svn_io_run_invoke_diff3(&exit_code, ".", + detranslated_target, + left_abspath, right_abspath, + target_label, left_label, right_label, + result_f, invoke_diff3_cmd, + scratch_pool)); + + *contains_conflicts = (exit_code == 1); - *contains_conflicts = exit_code == 1; - return SVN_NO_ERROR; } + /* Preserve the three pre-merge files. Create three empty files, with unique names that each include the @@ -843,11 +854,13 @@ merge_text_file(svn_skel_t **work_items, temp_dir, base_name, ".tmp", svn_io_file_del_none, pool, pool)); - /* Run the external or internal merge, as requested. */ - if (mt->diff3_cmd) + /* Run the external (old-style or new-style) or internal merge, as + requested. */ + if (mt->diff3_cmd || mt->invoke_diff3_cmd) SVN_ERR(do_text_merge_external(&contains_conflicts, result_f, mt->diff3_cmd, + mt->invoke_diff3_cmd, mt->merge_options, detranslated_target_abspath, left_abspath, @@ -1080,6 +1093,7 @@ svn_wc__internal_merge(svn_skel_t **work_items, apr_hash_t *old_actual_props, svn_boolean_t dry_run, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *merge_options, const apr_array_header_t *prop_diff, svn_cancel_func_t cancel_func, @@ -1106,6 +1120,7 @@ svn_wc__internal_merge(svn_skel_t **work_items, mt.old_actual_props = old_actual_props; mt.prop_diff = prop_diff; mt.diff3_cmd = diff3_cmd; + mt.invoke_diff3_cmd = invoke_diff3_cmd; mt.merge_options = merge_options; /* Decide if the merge target is a text or binary file. */ @@ -1121,7 +1136,9 @@ svn_wc__internal_merge(svn_skel_t **work_items, } SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt, - (! is_binary) && diff3_cmd != NULL, + (! is_binary) + && diff3_cmd != NULL + && invoke_diff3_cmd != NULL, target_abspath, cancel_func, cancel_baton, scratch_pool, scratch_pool)); @@ -1193,7 +1210,7 @@ svn_wc__internal_merge(svn_skel_t **work_items, svn_error_t * -svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome, +svn_wc_merge6(enum svn_wc_merge_outcome_t *merge_content_outcome, enum svn_wc_notify_state_t *merge_props_outcome, svn_wc_context_t *wc_ctx, const char *left_abspath, @@ -1206,6 +1223,7 @@ svn_error_t * const svn_wc_conflict_version_t *right_version, svn_boolean_t dry_run, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *merge_options, apr_hash_t *original_props, const apr_array_header_t *prop_diff, @@ -1345,6 +1363,7 @@ svn_error_t * old_actual_props, dry_run, diff3_cmd, + invoke_diff3_cmd, merge_options, prop_diff, cancel_func, cancel_baton, Index: subversion/libsvn_wc/update_editor.c =================================================================== --- subversion/libsvn_wc/update_editor.c (revision 1542103) +++ subversion/libsvn_wc/update_editor.c (working copy) @@ -223,6 +223,10 @@ struct edit_baton internal merge code is used). */ const char *diff3_cmd; + /* External custom invoke diff3 to use for merges (can be null, in + which case internal merge code is used). */ + const char *invoke_diff3_cmd; + /* Externals handler */ svn_wc_external_update_t external_func; void *external_baton; @@ -3900,6 +3904,7 @@ svn_wc__perform_file_merge(svn_skel_t **work_items svn_revnum_t target_revision, const apr_array_header_t *propchanges, const char *diff3_cmd, + const char *invoke_diff3_cmd, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, @@ -3977,7 +3982,8 @@ svn_wc__perform_file_merge(svn_skel_t **work_items oldrev_str, newrev_str, mine_str, old_actual_props, FALSE /* dry_run */, - diff3_cmd, NULL, propchanges, + diff3_cmd, invoke_diff3_cmd, + NULL, propchanges, cancel_func, cancel_baton, result_pool, scratch_pool)); @@ -4138,6 +4144,7 @@ merge_file(svn_skel_t **work_items, *eb->target_revision, fb->propchanges, eb->diff3_cmd, + eb->invoke_diff3_cmd, eb->cancel_func, eb->cancel_baton, result_pool, scratch_pool)); } /* end: working file exists and has mods */ @@ -4831,6 +4838,7 @@ make_editor(svn_revnum_t *target_revision, svn_wc_external_update_t external_func, void *external_baton, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *preserved_exts, const svn_delta_editor_t **editor, void **edit_baton, @@ -4901,6 +4909,7 @@ make_editor(svn_revnum_t *target_revision, eb->external_func = external_func; eb->external_baton = external_baton; eb->diff3_cmd = diff3_cmd; + eb->invoke_diff3_cmd = invoke_diff3_cmd; eb->cancel_func = cancel_func; eb->cancel_baton = cancel_baton; eb->conflict_func = conflict_func; @@ -5107,6 +5116,7 @@ svn_wc__get_update_editor(const svn_delta_editor_t svn_boolean_t server_performs_filtering, svn_boolean_t clean_checkout, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *preserved_exts, svn_wc_dirents_func_t fetch_dirents_func, void *fetch_dirents_baton, @@ -5131,7 +5141,8 @@ svn_wc__get_update_editor(const svn_delta_editor_t fetch_dirents_func, fetch_dirents_baton, conflict_func, conflict_baton, external_func, external_baton, - diff3_cmd, preserved_exts, editor, edit_baton, + diff3_cmd, invoke_diff3_cmd, + preserved_exts, editor, edit_baton, result_pool, scratch_pool); } @@ -5150,6 +5161,7 @@ svn_wc__get_switch_editor(const svn_delta_editor_t svn_boolean_t allow_unver_obstructions, svn_boolean_t server_performs_filtering, const char *diff3_cmd, + const char *invoke_diff3_cmd, const apr_array_header_t *preserved_exts, svn_wc_dirents_func_t fetch_dirents_func, void *fetch_dirents_baton, @@ -5178,7 +5190,8 @@ svn_wc__get_switch_editor(const svn_delta_editor_t fetch_dirents_func, fetch_dirents_baton, conflict_func, conflict_baton, external_func, external_baton, - diff3_cmd, preserved_exts, + diff3_cmd, invoke_diff3_cmd, + preserved_exts, editor, edit_baton, result_pool, scratch_pool); } Index: subversion/libsvn_wc/wc.h =================================================================== --- subversion/libsvn_wc/wc.h (revision 1542103) +++ subversion/libsvn_wc/wc.h (working copy) @@ -413,8 +413,8 @@ svn_wc__internal_file_modified_p(svn_boolean_t *mo When DRY_RUN is true, no actual changes are made to the working copy. - If DIFF3_CMD is specified, the given external diff3 tool will - be used instead of our built in diff3 routines. + If DIFF3_CMD or INVOKE_DIFF3_CMD is specified, the given external + diff3 tool will be used instead of our built in diff3 routines. When MERGE_OPTIONS are specified, they are used by the internal diff3 routines, or passed to the external diff3 tool. @@ -453,6 +453,7 @@ svn_wc__internal_merge(svn_skel_t **work_items, const char *target_label, apr_hash_t *old_actual_props, svn_boolean_t dry_run, + const char *invoke_diff3_cmd, const char *diff3_cmd, const apr_array_header_t *merge_options, const apr_array_header_t *prop_diff, @@ -461,6 +462,7 @@ svn_wc__internal_merge(svn_skel_t **work_items, apr_pool_t *result_pool, apr_pool_t *scratch_pool); + /* A default error handler for svn_wc_walk_entries3(). Returns ERR in all cases. */ svn_error_t * @@ -730,6 +732,7 @@ svn_wc__perform_file_merge(svn_skel_t **work_items svn_revnum_t target_revision, const apr_array_header_t *propchanges, const char *diff3_cmd, + const char *invoke_diff3_cmd, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, Index: subversion/libsvn_wc/wc_db_update_move.c =================================================================== --- subversion/libsvn_wc/wc_db_update_move.c (revision 1542103) +++ subversion/libsvn_wc/wc_db_update_move.c (working copy) @@ -961,8 +961,9 @@ update_working_file(const char *local_relpath, NULL, NULL, NULL, /* diff labels */ actual_props, FALSE, /* dry-run */ - NULL, /* diff3-cmd */ - NULL, /* merge options */ + NULL, /* diff3-cmd */ + NULL, /* invoke-diff3-cmd */ + NULL, /* merge options */ propchanges, NULL, NULL, /* cancel_func + baton */ scratch_pool, scratch_pool)); Index: subversion/svn/cl.h =================================================================== --- subversion/svn/cl.h (revision 1542103) +++ subversion/svn/cl.h (working copy) @@ -83,8 +83,12 @@ typedef enum svn_cl__accept_t svn_cl__accept_edit, /* Launch user's resolver and resolve conflict with edited file. */ - svn_cl__accept_launch + svn_cl__accept_launch, + /* Launch user's resolver with the invoke_diff3_cmd in the config + file and resolve conflict with edited file. */ + svn_cl__accept_invoke_diff3_config + } svn_cl__accept_t; /* --accept action user input words */ @@ -97,6 +101,7 @@ typedef enum svn_cl__accept_t #define SVN_CL__ACCEPT_THEIRS_FULL "theirs-full" #define SVN_CL__ACCEPT_EDIT "edit" #define SVN_CL__ACCEPT_LAUNCH "launch" +#define SVN_CL__ACCEPT_INVOKE_DIFF3_CONFIG "invoke-diff3-config" /* Return the svn_cl__accept_t value corresponding to WORD, using exact * case-sensitive string comparison. Return svn_cl__accept_invalid if WORD @@ -204,6 +209,9 @@ typedef struct svn_cl__opt_state_t svn_boolean_t revprop; /* operate on a revision property */ const char *merge_cmd; /* the external merge command to use (not converted to UTF-8) */ + const char *invoke_diff3_cmd; /* the format string for the external + merge command to use (not + converted to UTF-8) */ const char *editor_cmd; /* the external editor command to use (not converted to UTF-8) */ svn_boolean_t record_only; /* whether to record mergeinfo */ @@ -531,6 +539,21 @@ svn_cl__merge_file_externally(const char *base_pat svn_boolean_t *remains_in_conflict, apr_pool_t *pool); +/* As svn_cl__merge_file_externally, but for the invoke_diff3_cmd + selected merge tool */ +svn_error_t * +svn_cl__invoke_diff3_cmd_externally(const char *base_label, + const char *their_label, + const char *my_label, + const char *base_file, + const char *their_file, + const char *my_file, + apr_hash_t *config, + svn_boolean_t *remains_in_conflict, + const char *opt_code, + apr_pool_t *pool); + + /* Like svn_cl__merge_file_externally, but using a built-in merge tool * with help from an external editor specified by EDITOR_CMD. */ svn_error_t * Index: subversion/svn/conflict-callbacks.c =================================================================== --- subversion/svn/conflict-callbacks.c (revision 1542103) +++ subversion/svn/conflict-callbacks.c (working copy) @@ -118,6 +118,11 @@ svn_cl__accept_from_word(const char *word) if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) return svn_cl__accept_launch; + if (strcmp(word, SVN_CL__ACCEPT_INVOKE_DIFF3_CONFIG) == 0 + /* FIX ME: Which smiley to select? */ + || strcmp(word, "3f") == 0 || strcmp(word, ":-?") == 0) + return svn_cl__accept_launch; + /* word is an invalid action. */ return svn_cl__accept_invalid; } @@ -409,7 +414,38 @@ launch_resolver(svn_boolean_t *performed_edit, return SVN_NO_ERROR; } +/* Run an external merge tool, passing it the 'base', 'their', 'my' and + * 'merged' files in DESC. The tool to use is determined by B->config and + * environment variables; see svn_cl__merge_file_externally() for details. + * + * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not + * configured or cannot run, do not touch *PERFORMED_EDIT, report the error + * on stderr, and return SVN_NO_ERROR; if any other error is encountered, + * return that error. */ +static svn_error_t * +invoke_diff3_resolver(svn_boolean_t *performed_edit, + const svn_wc_conflict_description2_t *desc, + svn_cl__interactive_conflict_baton_t *b, + const char *opt_code, + apr_pool_t *pool) +{ + SVN_ERR(svn_cl__invoke_diff3_cmd_externally("BASE", + "OLD", + "NEW", + desc->base_abspath, + desc->their_abspath, + desc->my_abspath, + b->config, NULL, + opt_code, + pool)); + if (performed_edit) + *performed_edit = TRUE; + + return SVN_NO_ERROR; +} + + /* Maximum line length for the prompt string. */ #define MAX_PROMPT_WIDTH 70 @@ -458,6 +494,15 @@ static const resolver_option_t text_conflict_optio -1 }, { "l", N_("launch tool"), N_("launch external tool to resolve " "conflict [launch]"), -1 }, + { "3f", N_("invoke-diff3-cmd given in config file"), + N_("use invoke-diff3 command defined in " + "the config file to resolve conflict " + "[invoke-diff3-config]"), -1 }, + + { "i", N_("interactive invoke-diff3-cmd selection"), + N_("interactively select tool now to " + "resolve conflict"), -1 }, + { "p", N_("postpone"), N_("mark the conflict to be resolved later" " [postpone]"), svn_wc_conflict_choose_postpone }, @@ -734,6 +779,8 @@ handle_text_conflict(svn_wc_conflict_result_t *res *next_option++ = "df"; *next_option++ = "e"; *next_option++ = "m"; + *next_option++ = "3f"; + *next_option++ = "i"; if (knows_something) *next_option++ = "r"; @@ -798,8 +845,8 @@ handle_text_conflict(svn_wc_conflict_result_t *res if (! diff_allowed) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, - _("Invalid option; there's no " - "merged version to diff.\n\n"))); + _("Invalid option; there's no " + "merged version to diff.\n\n"))); continue; } @@ -887,8 +934,8 @@ handle_text_conflict(svn_wc_conflict_result_t *res { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", err->message ? err->message : - _("Error running merge tool, " - "try '(m) merge' instead."))); + _("Error running merge tool, " + "try '(m) merge' instead."))); svn_error_clear(err); } else if (err) @@ -901,6 +948,77 @@ handle_text_conflict(svn_wc_conflict_result_t *res SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option.\n\n"))); } + else if (strcmp(opt->code, "i") == 0) + { /* interactively get the invoke-diff3-cmd */ + + if (desc->base_abspath && desc->their_abspath && + desc->my_abspath && desc->merged_file) + { + const char *answer; + svn_error_t *err; + + SVN_ERR(svn_cmdline_prompt_user2(&answer, + "Enter the invoke-diff3-cmd: ", + b->pb, iterpool)); + err = invoke_diff3_resolver(&performed_edit, desc, b, answer, iterpool); + + if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", + err->message ? err->message : + _("Error executing the interactive, " + "invoke-diff3-cmd.\n"))); + svn_error_clear(err); + } + else if (err) + return svn_error_trace(err); + + if (performed_edit) + knows_something = TRUE; + } + else + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option.\n\n"))); + } + else if (strcmp(opt->code, "3f") == 0) + { + if (desc->base_abspath && desc->their_abspath && + desc->my_abspath && desc->merged_file) + { + + svn_error_t *err; + /* ### This check should be earlier as it's nasty to offer an option + * and then when the user chooses it say 'Invalid option'. */ + /* ### 'merged_file' shouldn't be necessary *before* we launch the + * resolver: it should be the *result* of doing so. */ + + err = invoke_diff3_resolver(&performed_edit, desc, b, opt->code, iterpool); + if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_DIFF3_TOOL) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", + err->message ? err->message : + _("No invoke-diff3 tool found, " + "try '(m) merge' instead.\n"))); + svn_error_clear(err); + } + else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) + { + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", + err->message ? err->message : + _("Error running invoke-diff3 tool, " + "try '(m) merge' instead."))); + svn_error_clear(err); + } + else if (err) + return svn_error_trace(err); + + if (performed_edit) + knows_something = TRUE; + } + else + SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, + _("Invalid option.\n\n"))); + } else if (opt->choice != -1) { if ((opt->choice == svn_wc_conflict_choose_mine_conflict @@ -921,9 +1039,9 @@ handle_text_conflict(svn_wc_conflict_result_t *res && ! knows_something) { SVN_ERR(svn_cmdline_fprintf( - stderr, iterpool, - _("Invalid option; use diff/edit/merge/launch " - "before choosing 'mark resolved'.\n\n"))); + stderr, iterpool, + _("Invalid option; use diff/edit/merge/launch " + "before choosing 'mark resolved'.\n\n"))); continue; } @@ -1294,6 +1412,57 @@ conflict_func_interactive(svn_wc_conflict_result_t } /* else, fall through to prompting. */ break; + case svn_cl__accept_invoke_diff3_config: + if (desc->base_abspath && desc->their_abspath + && desc->my_abspath && desc->merged_file) + { + svn_boolean_t remains_in_conflict; + + if (b->external_failed) + { + (*result)->choice = svn_wc_conflict_choose_postpone; + return SVN_NO_ERROR; + } + err = svn_cl__invoke_diff3_cmd_externally("BASE", + "NEW", + "OLD", + desc->base_abspath, + desc->their_abspath, + desc->my_abspath, + b->config, + &remains_in_conflict, + "3f", + scratch_pool); + if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL) + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + err->message ? err->message : + _("No invoke-diff3-cmd tool found;" + " leaving all conflicts."))); + b->external_failed = TRUE; + return svn_error_trace(err); + } + else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM) + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + err->message ? err->message : + _("Error running invoke-diff3-cmd tool;" + " leaving all conflicts."))); + b->external_failed = TRUE; + return svn_error_trace(err); + } + else if (err) + return svn_error_trace(err); + + if (remains_in_conflict) + (*result)->choice = svn_wc_conflict_choose_postpone; + else + (*result)->choice = svn_wc_conflict_choose_merged; + return SVN_NO_ERROR; + } + /* else, fall through to prompting. */ + break; + } /* Print a summary of conflicts before starting interactive resolution */ Index: subversion/svn/svn.c =================================================================== --- subversion/svn/svn.c (revision 1542103) +++ subversion/svn/svn.c (working copy) @@ -95,6 +95,7 @@ typedef enum svn_cl__longopt_t { opt_ignore_externals, opt_incremental, opt_merge_cmd, + opt_invoke_diff3_cmd, opt_native_eol, opt_new_cmd, opt_no_auth_cache, @@ -250,6 +251,24 @@ const apr_getopt_option_t svn_cl__options[] = {"ignore-externals", opt_ignore_externals, 0, N_("ignore externals definitions")}, {"diff3-cmd", opt_merge_cmd, 1, N_("use ARG as merge command")}, + {"invoke-diff3-cmd", opt_invoke_diff3_cmd, 1, + N_("use ARG as format string for external merge program\n" + " " + "invocation. Substitutions: \n" + " " + " %svn_mine 'mine' file\n" + " " + " %svn_yours 'yours' file\n" + " " + " %svn_base 'base' file\n" + " " + " %svn_label_mine label of the 'mine file\n" + " " + " %svn_label_yours label of the 'yours' file\n" + " " + " %svn_label_base label of the 'mine file\n" + " " + "See 'help diff' for example usage.")}, {"editor-cmd", opt_editor_cmd, 1, N_("use ARG as external editor")}, {"record-only", opt_record_only, 0, N_("merge only mergeinfo differences")}, @@ -546,8 +565,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table " for writing by another Subversion client.\n" " Note that the 'svn status' command shows unversioned items as '?',\n" " and ignored items as 'I' if the --no-ignore option is given to it.\n"), - {opt_merge_cmd, opt_remove_unversioned, opt_remove_ignored, - opt_include_externals, 'q'} }, + {opt_merge_cmd, opt_invoke_diff3_cmd, opt_remove_unversioned, + opt_remove_ignored, opt_include_externals, 'q'} }, { "commit", svn_cl__commit, {"ci"}, N_("Send changes from your working copy to the repository.\n" @@ -1140,8 +1159,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table " target. Also, merge-tracking is not supported for merges from foreign\n" " repositories.\n"), {'r', 'c', 'N', opt_depth, 'q', opt_force, opt_dry_run, opt_merge_cmd, - opt_record_only, 'x', opt_ignore_ancestry, opt_accept, opt_reintegrate, - opt_allow_mixed_revisions, 'v'} }, + opt_invoke_diff3_cmd, opt_record_only, 'x', opt_ignore_ancestry, + opt_accept, opt_reintegrate, opt_allow_mixed_revisions, 'v'} }, { "mergeinfo", svn_cl__mergeinfo, {0}, N_ ("Display merge-related information.\n" @@ -1604,8 +1623,9 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table " svn switch --relocate http:// svn://\n" " svn switch --relocate http://www.example.com/repo/project \\\n" " svn://svn.example.com/repo/project\n"), - { 'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_relocate, - opt_ignore_externals, opt_ignore_ancestry, opt_force, opt_accept}, + { 'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, + opt_invoke_diff3_cmd, opt_relocate, opt_ignore_externals, + opt_ignore_ancestry, opt_force, opt_accept}, {{opt_ignore_ancestry, N_("allow switching to a node with no common ancestor")}} }, @@ -1663,9 +1683,9 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table "\n" " Use the --set-depth option to set a new working copy depth on the\n" " targets of this operation.\n"), - {'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_force, - opt_ignore_externals, opt_changelist, opt_editor_cmd, opt_accept, - opt_parents} }, + {'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, + opt_invoke_diff3_cmd, opt_force, opt_ignore_externals, opt_changelist, + opt_editor_cmd, opt_accept, opt_parents} }, { "upgrade", svn_cl__upgrade, {0}, N_ ("Upgrade the metadata storage format for a working copy.\n" @@ -2174,6 +2194,9 @@ sub_main(int argc, const char *argv[], apr_pool_t case opt_merge_cmd: opt_state.merge_cmd = apr_pstrdup(pool, opt_arg); break; + case opt_invoke_diff3_cmd: + opt_state.invoke_diff3_cmd = apr_pstrdup(pool, opt_arg); + break; case opt_record_only: opt_state.record_only = TRUE; break; @@ -2611,6 +2634,16 @@ sub_main(int argc, const char *argv[], apr_pool_t return EXIT_ERROR(err); } + /* Check for mutually exclusive args --diff3-cmd and + --invoke-diff3-cmd */ + if (opt_state.merge_cmd && opt_state.invoke_diff3_cmd) + { + err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, + _("--diff3-cmd and --invoke-diff3-cmd are " + "mutually exclusive")); + return EXIT_ERROR(err); + } + /* Ensure that 'revision_ranges' has at least one item, and make 'start_revision' and 'end_revision' match that item. */ if (opt_state.revision_ranges->nelts == 0) @@ -2830,8 +2863,15 @@ sub_main(int argc, const char *argv[], apr_pool_t svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_INVOKE_DIFF_CMD, opt_state.diff.invoke_diff_cmd); if (opt_state.merge_cmd) - svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, - SVN_CONFIG_OPTION_DIFF3_CMD, opt_state.merge_cmd); + { + svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF3_CMD, opt_state.merge_cmd); + } + if (opt_state.invoke_diff3_cmd) + { + svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD, opt_state.invoke_diff3_cmd); + } if (opt_state.diff.internal_diff) svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF_CMD, NULL); @@ -2955,6 +2995,12 @@ sub_main(int argc, const char *argv[], apr_pool_t _("--accept=%s incompatible with" " --non-interactive"), SVN_CL__ACCEPT_LAUNCH)); + if (opt_state.accept_which == svn_cl__accept_invoke_diff3_config) + return EXIT_ERROR( + svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--accept=%s incompatible with" + " --non-interactive"), + SVN_CL__ACCEPT_LAUNCH)); /* The default action when we're non-interactive is to postpone * conflict resolution. */ Index: subversion/svn/util.c =================================================================== --- subversion/svn/util.c (revision 1542103) +++ subversion/svn/util.c (working copy) @@ -66,6 +66,7 @@ #include "private/svn_client_private.h" #include "private/svn_cmdline_private.h" #include "private/svn_string_private.h" +#include "private/svn_io_private.h" @@ -173,7 +174,105 @@ svn_cl__merge_file_externally(const char *base_pat return SVN_NO_ERROR; } +svn_error_t * +svn_cl__invoke_diff3_cmd_externally(const char *base_label, + const char *their_label, + const char *my_label, + const char *base_file, + const char *their_file, + const char *my_file, + apr_hash_t *config, + svn_boolean_t *remains_in_conflict, + const char *opt_code, + apr_pool_t *pool) +{ + char *invoke_diff3_cmd; + const char ** cmd; + apr_pool_t *scratch_pool = svn_pool_create(pool); + char *cwd; + int exitcode; + apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool); + if (status != 0) + return svn_error_wrap_apr(status, NULL); + + if (0 == strcmp(opt_code,"3f")) /* command in config file */ + { + + if (apr_env_get(&invoke_diff3_cmd, "SVN_INVOKE_DIFF3_CMD", pool) != APR_SUCCESS) + { + struct svn_config_t *cfg; + invoke_diff3_cmd = NULL; + cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; + /* apr_env_get wants char **, this wants const char ** */ + svn_config_get(cfg, (const char **)&invoke_diff3_cmd, + SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_INVOKE_DIFF3_CMD, NULL); + } + + if (invoke_diff3_cmd) + { + const char *c; + for (c = invoke_diff3_cmd; *c; c++) + if (!svn_ctype_isspace(*c)) + break; + + if (! *c) + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_DIFF3_TOOL, NULL, + _("The SVN_INVOKE_DIFF3_TOOL environment variable is empty or " + "consists solely of whitespace. Expected a shell command.\n")); + } + else + return svn_error_create + (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL, + _("The environment variable SVN_INVOKE_DIFF3_TOOL and the invoke-diff3-cmd run-time " + "configuration option were not set.\n")); + } + else + { + invoke_diff3_cmd = apr_pstrdup(pool, opt_code); + } + + + if (0 == strlen(invoke_diff3_cmd)) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, NULL); + + cmd = svn_io__create_custom_diff_cmd(my_label, their_label, base_label, + my_file, their_file, base_file, + invoke_diff3_cmd, scratch_pool); + + SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), + cmd[0], cmd, &exitcode, NULL, TRUE, + NULL, NULL, NULL, scratch_pool)); + + /* According to the diff3 docs, a '0' means the merge was clean, and + '1' means conflict markers were found. Anything else is real + error. */ + if ((exitcode != 0) && (exitcode != 1)) + { + + int i; + const char *failed_command = ""; + + for (i = 0; cmd[i]; ++i) + failed_command = apr_pstrcat(pool, failed_command, + cmd[i], " ", (char*) NULL); + svn_pool_destroy(scratch_pool); + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("'%s' was expanded to '%s' and returned %d"), + invoke_diff3_cmd, + failed_command, + exitcode); + } + else if (remains_in_conflict) + *remains_in_conflict = exitcode == 1; + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + + /* A svn_client_ctx_t's log_msg_baton3, for use with svn_cl__make_log_msg_baton(). */ struct log_msg_baton Index: subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout =================================================================== --- subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout (revision 1542103) +++ subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout (working copy) @@ -201,6 +201,15 @@ Valid options: 'empty', 'files', 'immediates', or 'infinity') -q [--quiet] : print nothing, or only summary information --diff3-cmd ARG : use ARG as merge command + --invoke-diff3-cmd ARG : use ARG as format string for external merge program + invocation. Substitutions: + %svn_mine 'mine' file + %svn_yours 'yours' file + %svn_base 'base' file + %svn_label_mine label of the 'mine file + %svn_label_yours label of the 'yours' file + %svn_label_base label of the 'mine file + See 'help diff' for example usage. --relocate : relocate via URL-rewriting --ignore-externals : ignore externals definitions --ignore-ancestry : allow switching to a node with no common ancestor