Hi, thanks for all the comments and help, here is the next attempt.
Gabriela [[[ Add new diff option "--invoke-diff-cmd" which allows the user to define a custom command line or config file entry for an external diff program. * subversion/include/svn_client.h (svn_client_diff7, svn_client_diff_peg7): Declare the new API. Like svn_client_diff[_peg]6 but with invoke_diff_cmd parameter. (svn_client_diff6, svn_client_diff_peg_6): Deprecate. * subversion/include/svn_config.h (SVN_CONFIG_OPTION_INVOKE_DIFF_CMD): New definition. * subversion/include/svn_io.h (svn_io_create_custom_diff_cmd): New function. (svn_io_run_external_diff): New function. * subversion/libsvn_client/deprecated.c (svn_client_diff6, svn_client_diff_peg6): New deprecation wrappers. * subversion/libsvn_client/diff.c (struct diff_cmd_baton): New member: 'invoke_diff_cmd'. (diff_content_changed): Call svn_io_run_external_diff if --invoke-diff-cmd option was specified, otherwise retain previous behaviour. (set_up_diff_cmd_and_options): Apply invoke-diff-cmd option preferentially. Old behavior unchanged. (svn_client_diff_peg_7): Rename and update from svn_client_diff_peg_6. Add new parameter: invoke_diff_cmd. (svn_client_diff7): Rename and update from svn_client_diff6, add new parameter 'invoke_diff_cmd'. (): Update all comments mentioning 'svn_client_diff6' to 'svn_client_diff7'. * subversion/libsvn_subr/config_file.c (svn_config_ensure): New entry: invoke-diff-cmd. * subversion/libsvn_subr/io.c (svn_io_create_custom_diff_cmd): New function. (svn_io_run_external_diff): New function. * subversion/svn/cl.h (struct svn_cl__opt_state_t.diff): New member: 'invoke_diff_cmd'. * subversion/svn/diff-cmd.c (svn_cl__diff): Update call to svn_client_diff6 to svn_client_diff7. * subversion/svn/svn.c (svn_cl__options[]): Add help info and new variable: 'opt_invoke_diff_cmd'. (svn_cl__cmd_table[]): New option: 'invoke-diff-cmd'. (sub_main): Prohibit simultaneous usage of --invoke-diff-cmd and --internal-diff. Add new opt_state.diff.invoke-diff-cmd option to the option selector. Add call to svn_config_set. * subversion/tests/cmdline/diff_tests.py (diff_invoke_external_diffcmd): New function. (test_list): Add new entry 'diff_invoke_external_diffcmd'. * tools/hook-scripts/argv_dump.pl (New File): Perl script that enumerates output by svn to assist with testing. ]]]
Index: subversion/include/svn_client.h =================================================================== --- subversion/include/svn_client.h (revision 1480974) +++ subversion/include/svn_client.h (working copy) @@ -2986,6 +2986,11 @@ svn_client_blame(const char *path_or_url, * The above two options are mutually exclusive. It is an error to set * both to TRUE. * + * @a invoke_diff_cmd is used to call an external diff program but may + * not be @c NULL. The command line invocation will override the + * invoke-diff-cmd invocation entry(if any) in the Subversion + * configuration file. + * * Generated headers are encoded using @a header_encoding. * * Diff output will not be generated for binary files, unless @a @@ -3016,8 +3021,38 @@ svn_client_blame(const char *path_or_url, * @note @a relative_to_dir doesn't affect the path index generated by * external diff programs. * + * @since New in 1.9. + */ +svn_error_t * +svn_client_diff7(const apr_array_header_t *options, + const char *path_or_url1, + const svn_opt_revision_t *revision1, + const char *path_or_url2, + const svn_opt_revision_t *revision2, + const char *relative_to_dir, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + const apr_array_header_t *changelists, + const char *invoke_diff_cmd, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + +/** Similar to svn_client_diff7(), but with @a invoke_diff_cmd. + * + * @deprecated Provided for backward compatibility with the 1.8 API. * @since New in 1.8. */ +SVN_DEPRECATED svn_error_t * svn_client_diff6(const apr_array_header_t *diff_options, const char *path_or_url1, @@ -3175,14 +3210,47 @@ svn_client_diff(const apr_array_header_t *diff_opt * be either a working-copy path or URL. * * If @a peg_revision is #svn_opt_revision_unspecified, behave - * identically to svn_client_diff6(), using @a path_or_url for both of that + * identically to svn_client_diff7(), using @a path_or_url for both of that * function's @a path_or_url1 and @a path_or_url2 arguments. * - * All other options are handled identically to svn_client_diff6(). + * All other options are handled identically to svn_client_diff7(). * - * @since New in 1.8. + * @since New in 1.9. */ svn_error_t * +svn_client_diff_peg7(const apr_array_header_t *diff_options, + const char *path_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *start_revision, + const svn_opt_revision_t *end_revision, + const char *relative_to_dir, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + const apr_array_header_t *changelists, + const char *invoke_diff_cmd, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + + +/** Similar to svn_client_peg5(), but with @a no_diff_added set to + * FALSE, @a ignore_properties set to FALSE and @a properties_only + * set to false. + * + * @deprecated Provided for backward compatibility with the 1.7 API. + * @since New in 1.9. + */ +SVN_DEPRECATED +svn_error_t * svn_client_diff_peg6(const apr_array_header_t *diff_options, const char *path_or_url, const svn_opt_revision_t *peg_revision, Index: subversion/include/svn_config.h =================================================================== --- subversion/include/svn_config.h (revision 1480974) +++ subversion/include/svn_config.h (working copy) @@ -112,6 +112,8 @@ typedef struct svn_config_t svn_config_t; #define SVN_CONFIG_OPTION_DIFF_EXTENSIONS "diff-extensions" #define SVN_CONFIG_OPTION_DIFF3_CMD "diff3-cmd" #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" #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_io.h =================================================================== --- subversion/include/svn_io.h (revision 1480974) +++ subversion/include/svn_io.h (working copy) @@ -2278,6 +2278,38 @@ svn_io_file_readline(apr_file_t *file, /** @} */ +/** Parse a user defined command to contain dynamically created labels + * and filenames. + * + * @since New in 1.9. + */ +const char ** +svn_io_create_custom_diff_cmd(const char *label1, + const char *label2, + const char *label3, + const char *tmpfile1, + const char *tmpfile2, + const char *base, + const char *cmd, + apr_pool_t *scratch_pool); + +/** Run the external diff command defined by the invoke-diff-cmd + * option. + * + * @since New in 1.9. + */ +svn_error_t * +svn_io_run_external_diff(const char *dir, + const char *label1, + const char *label2, + const char *tmpfile1, + const char *tmpfile2, + int *pexitcode, + apr_file_t *outfile, + apr_file_t *errfile, + const char *external_diff_cmd, + apr_pool_t *scratch_pool); + #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/libsvn_client/deprecated.c =================================================================== --- subversion/libsvn_client/deprecated.c (revision 1480974) +++ subversion/libsvn_client/deprecated.c (working copy) @@ -914,6 +914,53 @@ svn_client_delete(svn_client_commit_info_t **commi /*** From diff.c ***/ svn_error_t * +svn_client_diff6(const apr_array_header_t *options, + const char *path_or_url1, + const svn_opt_revision_t *revision1, + const char *path_or_url2, + const svn_opt_revision_t *revision2, + const char *relative_to_dir, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + const apr_array_header_t *changelists, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + return svn_client_diff7(options, + path_or_url1, + revision1, + path_or_url2, + revision2, + relative_to_dir, + depth, + ignore_ancestry, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + use_git_diff_format, + header_encoding, + outstream, + errstream, + changelists, + NULL, + ctx, + pool); +} + +svn_error_t * svn_client_diff5(const apr_array_header_t *diff_options, const char *path1, const svn_opt_revision_t *revision1, @@ -1036,6 +1083,53 @@ svn_client_diff(const apr_array_header_t *options, } svn_error_t * +svn_client_diff_peg6(const apr_array_header_t *options, + const char *path_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *start_revision, + const svn_opt_revision_t *end_revision, + const char *relative_to_dir, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + const apr_array_header_t *changelists, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + return svn_client_diff_peg7(options, + path_or_url, + peg_revision, + start_revision, + end_revision, + relative_to_dir, + depth, + ignore_ancestry, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + use_git_diff_format, + header_encoding, + outstream, + errstream, + changelists, + NULL, + ctx, + pool); +} + +svn_error_t * svn_client_diff_peg5(const apr_array_header_t *diff_options, const char *path, const svn_opt_revision_t *peg_revision, Index: subversion/libsvn_client/diff.c =================================================================== --- subversion/libsvn_client/diff.c (revision 1480974) +++ subversion/libsvn_client/diff.c (working copy) @@ -427,7 +427,7 @@ print_git_diff_header(svn_stream_t *os, /* A helper func that writes out verbal descriptions of property diffs to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was - passed to svn_client_diff6(), which is probably stdout. + passed to svn_client_diff7(), which is probably stdout. ### FIXME needs proper docstring @@ -563,7 +563,7 @@ struct diff_cmd_baton { const char *orig_path_2; /* These are the numeric representations of the revisions passed to - svn_client_diff6(), either may be SVN_INVALID_REVNUM. We need these + svn_client_diff7(), either may be SVN_INVALID_REVNUM. We need these because some of the svn_wc_diff_callbacks4_t don't get revision arguments. @@ -611,6 +611,10 @@ struct diff_cmd_baton { /* Whether the local diff target of a repos->wc diff is a copy. */ svn_boolean_t repos_wc_diff_target_is_copy; + + /* external custom diff command */ + const char *invoke_diff_cmd; + }; /* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added @@ -786,7 +790,7 @@ diff_content_changed(svn_boolean_t *wrote_header, } - if (diff_cmd_baton->diff_cmd) + if (diff_cmd_baton->diff_cmd || diff_cmd_baton->invoke_diff_cmd) { apr_file_t *outfile; apr_file_t *errfile; @@ -815,15 +819,25 @@ diff_content_changed(svn_boolean_t *wrote_header, SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL, svn_io_file_del_on_pool_cleanup, scratch_pool, scratch_pool)); - - SVN_ERR(svn_io_run_diff2(".", - diff_cmd_baton->options.for_external.argv, - diff_cmd_baton->options.for_external.argc, - label1, label2, - tmpfile1, tmpfile2, - &exitcode, outfile, errfile, - diff_cmd_baton->diff_cmd, scratch_pool)); - + + if (diff_cmd_baton->diff_cmd) + SVN_ERR(svn_io_run_diff2(".", + diff_cmd_baton->options.for_external.argv, + diff_cmd_baton->options.for_external.argc, + label1, label2, + tmpfile1, tmpfile2, + &exitcode, outfile, errfile, + diff_cmd_baton->diff_cmd, scratch_pool)); + else + { + SVN_ERR( + svn_io_run_external_diff(".", + label1, label2, + tmpfile1, tmpfile2, + &exitcode, outfile, errfile, + diff_cmd_baton->invoke_diff_cmd, + scratch_pool)); + } SVN_ERR(svn_io_file_close(outfile, scratch_pool)); SVN_ERR(svn_io_file_close(errfile, scratch_pool)); @@ -1519,8 +1533,8 @@ diff_prepare_repos_repos(const char **url1, /* A Theoretical Note From Ben, regarding do_diff(). - This function is really svn_client_diff6(). If you read the public - API description for svn_client_diff6(), it sounds quite Grand. It + This function is really svn_client_diff7(). If you read the public + API description for svn_client_diff7(), it sounds quite Grand. It sounds really generalized and abstract and beautiful: that it will diff any two paths, be they working-copy paths or URLs, at any two revisions. @@ -1544,7 +1558,7 @@ diff_prepare_repos_repos(const char **url1, pigeonholed into one of these use-cases, we currently bail with a friendly apology. - Perhaps someday a brave soul will truly make svn_client_diff6() + Perhaps someday a brave soul will truly make svn_client_diff7() perfectly general. For now, we live with the 90% case. Certainly, the commandline client only calls this function in legal ways. When there are other users of svn_client.h, maybe this will become @@ -1557,7 +1571,7 @@ static svn_error_t * unsupported_diff_error(svn_error_t *child_err) { return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err, - _("Sorry, svn_client_diff6 was called in a way " + _("Sorry, svn_client_diff7 was called in a way " "that is not yet supported")); } @@ -1566,7 +1580,7 @@ unsupported_diff_error(svn_error_t *child_err) PATH1 and PATH2 are both working copy paths. REVISION1 and REVISION2 are their respective revisions. - All other options are the same as those passed to svn_client_diff6(). */ + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * diff_wc_wc(const char *path1, const svn_opt_revision_t *revision1, @@ -1649,7 +1663,7 @@ diff_wc_wc(const char *path1, and the actual two paths compared are determined by following copy history from PATH_OR_URL2. - All other options are the same as those passed to svn_client_diff6(). */ + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * diff_repos_repos(const svn_wc_diff_callbacks4_t *callbacks, struct diff_cmd_baton *callback_baton, @@ -1794,7 +1808,7 @@ diff_repos_repos(const svn_wc_diff_callbacks4_t *c revision, and the actual repository path to be compared is determined by following copy history. - All other options are the same as those passed to svn_client_diff6(). */ + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * diff_repos_wc(const char *path_or_url1, const svn_opt_revision_t *revision1, @@ -2129,7 +2143,7 @@ do_diff(const svn_wc_diff_callbacks4_t *callbacks, revision, and the actual repository path to be compared is determined by following copy history. - All other options are the same as those passed to svn_client_diff6(). */ + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * diff_summarize_repos_wc(svn_client_diff_summarize_func_t summarize_func, void *summarize_baton, @@ -2173,7 +2187,7 @@ diff_summarize_repos_wc(svn_client_diff_summarize_ PATH1 and PATH2 are both working copy paths. REVISION1 and REVISION2 are their respective revisions. - All other options are the same as those passed to svn_client_diff6(). */ + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * diff_summarize_wc_wc(svn_client_diff_summarize_func_t summarize_func, void *summarize_baton, @@ -2448,7 +2462,7 @@ set_up_diff_cmd_and_options(struct diff_cmd_baton { const char *diff_cmd = NULL; - /* See if there is a diff command and/or diff arguments. */ + /* old style diff_cmd has precedence in config file */ if (config) { svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); @@ -2455,15 +2469,14 @@ set_up_diff_cmd_and_options(struct diff_cmd_baton svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF_CMD, NULL); if (options == NULL) - { - const char *diff_extensions; - svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS, - SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL); - if (diff_extensions) - options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, pool); - } + { + const char *diff_extensions; + svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL); + if (diff_extensions) + options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, pool); + } } - if (options == NULL) options = apr_array_make(pool, 0, sizeof(const char *)); @@ -2470,8 +2483,22 @@ set_up_diff_cmd_and_options(struct diff_cmd_baton if (diff_cmd) SVN_ERR(svn_path_cstring_to_utf8(&diff_cmd_baton->diff_cmd, diff_cmd, pool)); - else - diff_cmd_baton->diff_cmd = NULL; + else { + if (config) /* check if there is a invoke_diff_cmd in the config file */ + { + svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); + diff_cmd_baton->diff_cmd = NULL; + svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, + SVN_CONFIG_OPTION_INVOKE_DIFF_CMD, NULL); + if (diff_cmd) + { + SVN_ERR(svn_path_cstring_to_utf8( + &diff_cmd_baton->invoke_diff_cmd, diff_cmd, pool)); + + return SVN_NO_ERROR; + } + } + } /* If there was a command, arrange options to pass to it. */ if (diff_cmd_baton->diff_cmd) @@ -2537,7 +2564,7 @@ set_up_diff_cmd_and_options(struct diff_cmd_baton * These cases require server communication. */ svn_error_t * -svn_client_diff6(const apr_array_header_t *options, +svn_client_diff7(const apr_array_header_t *options, const char *path_or_url1, const svn_opt_revision_t *revision1, const char *path_or_url2, @@ -2556,6 +2583,7 @@ svn_error_t * svn_stream_t *outstream, svn_stream_t *errstream, const apr_array_header_t *changelists, + const char *invoke_diff_cmd, svn_client_ctx_t *ctx, apr_pool_t *pool) { @@ -2573,7 +2601,8 @@ svn_error_t * /* setup callback and baton */ diff_cmd_baton.orig_path_1 = path_or_url1; diff_cmd_baton.orig_path_2 = path_or_url2; - + diff_cmd_baton.invoke_diff_cmd = invoke_diff_cmd; + SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options, ctx->config, pool)); diff_cmd_baton.pool = pool; @@ -2604,7 +2633,7 @@ svn_error_t * } svn_error_t * -svn_client_diff_peg6(const apr_array_header_t *options, +svn_client_diff_peg7(const apr_array_header_t *options, const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start_revision, @@ -2623,6 +2652,7 @@ svn_error_t * svn_stream_t *outstream, svn_stream_t *errstream, const apr_array_header_t *changelists, + const char *invoke_diff_cmd, svn_client_ctx_t *ctx, apr_pool_t *pool) { @@ -2636,6 +2666,7 @@ svn_error_t * /* setup callback and baton */ diff_cmd_baton.orig_path_1 = path_or_url; diff_cmd_baton.orig_path_2 = path_or_url; + diff_cmd_baton.invoke_diff_cmd = invoke_diff_cmd; SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options, ctx->config, pool)); Index: subversion/libsvn_subr/config_file.c =================================================================== --- subversion/libsvn_subr/config_file.c (revision 1480974) +++ subversion/libsvn_subr/config_file.c (working copy) @@ -1084,6 +1084,11 @@ svn_config_ensure(const char *config_dir, apr_pool "### Set diff3-has-program-arg to 'yes' if your 'diff3' program" NL "### accepts the '--diff-program' option." NL "# diff3-has-program-arg = [yes | no]" NL + "### Set invoke-diff-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 diff implementation." NL + "# invoke-diff-cmd = \"diff -y --label %l1% %f1% --label %l2% %f2%\""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 1480974) +++ subversion/libsvn_subr/io.c (working copy) @@ -2921,8 +2921,165 @@ svn_io_run_diff2(const char *dir, return SVN_NO_ERROR; } +const char ** +svn_io_create_custom_diff_cmd(const char *label1, + const char *label2, + const char *label3, + const char *tmpfile1, + const char *tmpfile2, + const char *base, + const char *cmd, + apr_pool_t *pool) +{ + apr_pool_t *subpool; + static const char *token_list[] = + {"%f1%","%f2%", "%f3%", "%l1%", "%l2%","%l3%" }; + const char *delimiters[] = {"#","^","&","!","~","?","$","*","|"}; + svn_stringbuf_t *com; + apr_array_header_t *tmp; + const char ** ret; + int i, delimiter; + char * found; + /* ensure that cmd is not empty; if so, let caller deal with it. */ + if (strlen(cmd) == 0) + { + ret = apr_pcalloc(pool, sizeof(char *)); + ret[0] = NULL; + return ret; + } + + subpool = svn_pool_create(pool); + delimiter = 0; + + /* find a delimiter that does not occur in the user string. */ + while ( ((found = strstr(cmd, delimiters[delimiter++]))) && + (delimiter < 9 /* sizeof(delimiters) */ ) ) + { ; } delimiter--; + + tmp = svn_cstring_split(cmd," ",TRUE, subpool); + com = svn_stringbuf_create_empty(subpool); + + /* ensure that the command is split into the correct argv parcels + * and leave a marker in place because the correct order is lost in + * the subsequent in-place replacements due to labels having + * spaces and ending up as 2 entries in ret[] */ + for (i = 0; i < tmp->nelts ; i++) + { + svn_stringbuf_appendcstr(com, APR_ARRAY_IDX(tmp, i, char *)); + svn_stringbuf_appendcstr(com, delimiters[delimiter]); + } + + for (i = 0; i < 6 /* sizeof(token_list) */; i++) + { + svn_stringbuf_t *token; + int len; + + token = svn_stringbuf_create_empty(subpool); + svn_stringbuf_appendcstr(token, token_list[i]); + len = 0; + while ( (found = strstr(com->data, token->data)) && + (strlen(found) > len) ) + { + len = strlen(found); + + /* if we find a % in front of this, consume it */ + if (com->data[com->len - strlen(found)-1] == '%') + svn_stringbuf_remove(com, strlen(found)-1, 1); + else if (i == 0) /* %f1 */ + svn_stringbuf_replace(com, com->len - strlen(found), token->len, + tmpfile1, strlen(tmpfile1)); + else if (i == 1) /* %f2 */ + svn_stringbuf_replace(com, com->len - strlen(found), token->len, + tmpfile2, strlen(tmpfile2)); + else if (i == 2) /* %f3 */ + svn_stringbuf_replace(com, com->len - strlen(found), token->len, + base, strlen(base)); + else if (i == 3) /* %l1 */ + svn_stringbuf_replace(com, com->len - strlen(found), token->len, + label1, strlen(label1)); + else if (i == 4) /* %l2 */ + svn_stringbuf_replace(com, com->len - strlen(found), token->len, + label2, strlen(label2)); + else if (i == 5) /* %l3 */ + svn_stringbuf_replace(com, com->len - strlen(found), token->len, + label3, strlen(label3)); + } + } + + /* Produce a null-terminated argv[] required by apr_proc_create() */ + tmp = svn_cstring_split(com->data,delimiters[delimiter],TRUE, pool); + + ret = apr_pcalloc(pool, + tmp->nelts * + tmp->elt_size*sizeof(char *)); + + for (i = 0; i < tmp->nelts ; i++) + ret[i] = APR_ARRAY_IDX(tmp, i, char *); + ret[i] = NULL; + + svn_pool_destroy(subpool); + return ret; +} + svn_error_t * +svn_io_run_external_diff(const char *dir, + const char *label1, + const char *label2, + const char *tmpfile1, + const char *tmpfile2, + int *pexitcode, + apr_file_t *outfile, + apr_file_t *errfile, + const char *external_diff_cmd, + apr_pool_t *pool) +{ + int exitcode; + const char ** cmd; + svn_error_t * se; + + if (0 == strlen(external_diff_cmd)) { + + se = svn_error_createf(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, + _("The --invoke-diff-cmd string was empty.\n")); + return se; + + } + cmd = svn_io_create_custom_diff_cmd(label1, label2, NULL, + tmpfile1, tmpfile2, NULL, + external_diff_cmd, pool); + if (pexitcode == NULL) + pexitcode = &exitcode; + + SVN_ERR(svn_io_run_cmd(dir, cmd[0], cmd, pexitcode, NULL, TRUE, + NULL, outfile, errfile, pool)); + + if (*pexitcode != 0 && *pexitcode != 1) + { + int i, size; + char * failed_command; + + for (i = 0, size = 0; cmd[i]; i++) + size += strlen(cmd[i]) + 1; + + failed_command = apr_pcalloc(pool, size * sizeof(char *)); + + for (i = 0; cmd[i]; i++) + { + strcat(failed_command, cmd[i]); + strcat(failed_command, " "); + } + + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("'%s' was expanded to '%s' and returned %d\n"), + external_diff_cmd, + svn_dirent_local_style(failed_command, pool), + *pexitcode); + } + return SVN_NO_ERROR; +} + +svn_error_t * svn_io_run_diff3_3(int *exitcode, const char *dir, const char *mine, Index: subversion/svn/cl.h =================================================================== --- subversion/svn/cl.h (revision 1480974) +++ subversion/svn/cl.h (working copy) @@ -184,6 +184,8 @@ typedef struct svn_cl__opt_state_t { const char *diff_cmd; /* the external diff command to use (not converted to UTF-8) */ + const char *invoke_diff_cmd; /* the format string to specify args */ + /* for the external diff cmd */ svn_boolean_t internal_diff; /* override diff_cmd in config file */ svn_boolean_t no_diff_added; /* do not show diffs for deleted files */ svn_boolean_t no_diff_deleted; /* do not show diffs for deleted files */ Index: subversion/svn/diff-cmd.c =================================================================== --- subversion/svn/diff-cmd.c (revision 1480974) +++ subversion/svn/diff-cmd.c (working copy) @@ -404,7 +404,7 @@ svn_cl__diff(apr_getopt_t *os, ctx, iterpool)); } else - SVN_ERR(svn_client_diff6( + SVN_ERR(svn_client_diff7( options, target1, &(opt_state->start_revision), @@ -424,6 +424,7 @@ svn_cl__diff(apr_getopt_t *os, outstream, errstream, opt_state->changelists, + opt_state->diff.invoke_diff_cmd, ctx, iterpool)); } else @@ -455,7 +456,7 @@ svn_cl__diff(apr_getopt_t *os, ctx, iterpool)); } else - SVN_ERR(svn_client_diff_peg6( + SVN_ERR(svn_client_diff_peg7( options, truepath, &peg_revision, @@ -475,6 +476,7 @@ svn_cl__diff(apr_getopt_t *os, outstream, errstream, opt_state->changelists, + opt_state->diff.invoke_diff_cmd, ctx, iterpool)); } } Index: subversion/svn/svn.c =================================================================== --- subversion/svn/svn.c (revision 1480974) +++ subversion/svn/svn.c (working copy) @@ -84,6 +84,7 @@ typedef enum svn_cl__longopt_t { opt_ignore_properties, opt_properties_only, opt_patch_compatible, + opt_invoke_diff_cmd, /* end of diff options */ opt_dry_run, opt_editor_cmd, @@ -336,6 +337,25 @@ const apr_getopt_option_t svn_cl__options[] = {"diff", opt_diff, 0, N_("produce diff output")}, /* maps to show_diff */ /* diff options */ {"diff-cmd", opt_diff_cmd, 1, N_("use ARG as diff command")}, + {"invoke-diff-cmd", opt_invoke_diff_cmd, 1, + N_("use ARG as format string for external diff command\n" + " " + "invocation. \n \n" + " " + "Substitutions: %f1% %f2% files to compare \n" + " " + " %l1% %l2% user defined labels \n" + " " + "Examples: --invoke-diff-cmd=\"diff -y %f1% %f2% \n" + " " + " --invoke-diff-cmd=\"kdiff3 -auto -o /home/u/log \\ \n" + " " + " %f1% %f2% --L1 %l1% --L2 \"Custom Label\" \" \n" + " " + "The switch symbol '%' can be escaped in the usual way \n" + " " + "using the '%' character: %%f1% will be passed as %f1%. \n" + )}, {"internal-diff", opt_internal_diff, 0, N_("override diff-cmd specified in config file")}, {"no-diff-added", opt_no_diff_added, 0, @@ -575,7 +595,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table opt_internal_diff, 'x', opt_no_diff_added, opt_no_diff_deleted, opt_ignore_properties, opt_properties_only, opt_show_copies_as_adds, opt_notice_ancestry, opt_summarize, opt_changelist, - opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible} }, + opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible, + opt_invoke_diff_cmd} }, { "export", svn_cl__export, {0}, N_ ("Create an unversioned copy of a tree.\n" "usage: 1. export [-r REV] URL[@PEGREV] [PATH]\n" @@ -2093,6 +2114,9 @@ sub_main(int argc, const char *argv[], apr_pool_t case opt_diff_cmd: opt_state.diff.diff_cmd = apr_pstrdup(pool, opt_arg); break; + case opt_invoke_diff_cmd: + opt_state.diff.invoke_diff_cmd = apr_pstrdup(pool, opt_arg); + break; case opt_merge_cmd: opt_state.merge_cmd = apr_pstrdup(pool, opt_arg); break; @@ -2502,6 +2526,14 @@ sub_main(int argc, const char *argv[], apr_pool_t return EXIT_ERROR(err); } + if (opt_state.diff.invoke_diff_cmd && opt_state.diff.internal_diff) + { + err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--invoke-diff-cmd and --internal-diff " + "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) @@ -2714,9 +2746,17 @@ sub_main(int argc, const char *argv[], apr_pool_t /* XXX: Only diff_cmd for now, overlay rest later and stop passing opt_state altogether? */ - if (opt_state.diff.diff_cmd) + if (opt_state.diff.diff_cmd) + { svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF_CMD, opt_state.diff.diff_cmd); + } + else + { + if (opt_state.diff.invoke_diff_cmd) + 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); Index: subversion/tests/cmdline/diff_tests.py =================================================================== --- subversion/tests/cmdline/diff_tests.py (revision 1480974) +++ subversion/tests/cmdline/diff_tests.py (working copy) @@ -3260,7 +3260,30 @@ def diff_external_diffcmd(sbox): 'diff', '--diff-cmd', diff_script_path, iota_path) +# Check the correct parsing of arguments for an external diff tool +def diff_invoke_external_diffcmd(sbox): + "svn diff --diff-invoke-cmd passes correct args" + sbox.build(read_only = True) + os.chdir(sbox.wc_dir) + + iota_path = 'iota' + svntest.main.file_append(iota_path, "new text in iota") + + expected_output = svntest.verify.ExpectedOutput([ + "Index: iota\n", + "===================================================================\n", + "Dumping @ARGV...\n", + "Arg 0 is >iota (revision 1)<\n", + "Arg 1 is >"+os.path.abspath(svntest.wc.text_base_path("iota")) + "<\n", + "Arg 2 is >iota (working copy)<\n", + "Arg 3 is >"+os.path.abspath("iota") + "<\n"]) + + svntest.actions.run_and_verify_svn(None, expected_output, [], + 'diff', + '--invoke-diff-cmd=/home/g/trunk_ziff/tools/hook-scripts/argv_dump.pl %l1% %f1% %l2% %f2%', + iota_path) + #---------------------------------------------------------------------- # Diffing an unrelated repository URL against working copy with # local modifications (i.e. not committed). This is issue #3295 (diff @@ -4560,6 +4583,7 @@ test_list = [ None, diff_file_depth_empty, diff_wrong_extension_type, diff_external_diffcmd, + diff_invoke_external_diffcmd, diff_url_against_local_mods, diff_preexisting_rev_against_local_add, diff_git_format_wc_wc, Index: tools/hook-scripts/argv_dump.pl =================================================================== --- tools/hook-scripts/argv_dump.pl (revision 0) +++ tools/hook-scripts/argv_dump.pl (working copy) @@ -0,0 +1,33 @@ +#!/usr/bin/perl -w +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# Use this script to test svn output. For example: +# +# svn diff / +# '--invoke-diff-cmd=/home/g/trunk_ziff/tools/hook_scripts/argv_dump.pl %f1% %f2%' +# +use strict; +use warnings; + +print "Dumping \@ARGV...\n"; +for (my $i = 0; $i < @ARGV; $i++) { + print "Arg $i is >$ARGV[$i]<\n"; +} +exit 0; Property changes on: tools/hook-scripts/argv_dump.pl ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property