Hi,
I'm not sure how to update my branch because the underlying base has
changed(and doesn't merge all that well) and thus, I restarted with
just a patch and a copy of the current trunk.
So, just to keep things simple, I just post it as a patch for now.
Gabriela
Ps.: I'll take a look at the --invoke-diff3-cmd part this week sometime.
======================================================================
Introduction to --invoke-diff-cmd
======================================================================
--invoke-diff-cmd allows command line selection of an external diff
program and will be extended to cover the existing diff3 option with a
similar --invoke-diff3-cmd option.
Currently this capability is provided by user written shell scripts
which are passed as the diff program via the svn config file.
--invoke-diff-cmd is currently implemented for 'diff', 'log',
'svnlook' and the config file.
See: http://subversion.tigris.org/issues/show_bug.cgi?id=2044 for the
original motivation for this project.
What --invoke-diff-cmd provides
===============================
Users can type 'free-style' command lines for their selected
diff/merge program, and optionally select a diff command file that
applies stored commands to selected files. If the file diffed is not
in the list, the given command will be used instead.
from 'svn help diff':
--invoke-diff-cmd ARG:
use ARG as format string for external diff command
invocation.
Substitutions: %svn_new new file
%svn_old old file
%svn_label_new label of the new file
%svn_label_old label of the old file
Examples:
--invoke-diff-cmd='diff -y %svn_new %svn_old'
--invoke-diff-cmd="kdiff3 -auto -o /home/u/log \
%svn_new %svn_old --L1 %svn_new_label \
--L2 "Custom Label" '
Substitution variables may be embedded in strings:
+%svn_new, %svn_new- and file=%svn_label_new+
Structure of the feature:
=========================
API components
--------------
./subversion/libsvn_subr/io.c __create_custom_diff_cmd()
transforms the user input 'invoke-diff-cmd' into a command line
call by substitution the labels and file names(where defined),
whilst leaving everything else untouched.
./subversion/libsvn_subr/io.c svn_io_run_external_diff()
calls __create_custom_diff_cmd() and does all the error checking
required, before routing the result to the actual call to the APR
routine that makes it.
UI components
-------------
--invoke-diff-cmd and its user interface components for the command
line have been installed everywhere where --diff-cmd is available,
in svnlook.c, svn.c, svnlog.c.
Tests
=====
The test for the 'invoke-diff-cmd' feature is
/subversion/tests/cmdline/diff_tests.py diff_invoke_external_diffcmd
[[[
* subversion/include/private/svn_io_private.h
(svn_io__create_custom_diff_cmd): New function declaration.
* subversion/include/svn_config.h
(SVN_CONFIG_OPTION_INVOKE_DIFF_CMD): New definition.
* subversion/include/svn_error_codes.h
(SVN_CLIENT_DIFF_CMD): New macro.
* subversion/include/svn_io.h
(svn_io_run_external_diff): New function.
* subversion/libsvn_client/diff.c
(diff_writer_info_t): New member: 'invoke_diff_cmd'.
(create_diff_writer_info): Add routine to read invoke_diff_cmd
either from the commandline (preferential) or from the config
file, after it is established that the diff-cmd is not defined on
the commandline or in the .subversion/config file. Readjust the
flow of the function to ensure that the internal diff routine is
called if neither diff_cmd or invoke_diff_cmd are called.
(diff_content_changed): Raise an error if both diff_cmd and
invoke-diff-cmd are set. Add invoke-diff-cmd to if condition.
Add comment explaining the presence of non-canonical path in
function call. Call svn_io_run_external_diff if --invoke-diff-cmd
option was specified, otherwise retain previous behaviour.
* subversion/libsvn_subr/config_file.c
(svn_config_ensure,"invoke-diff-cmd"): New entry: invoke-diff-cmd.
Add help info.
* 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/log-cmd.c
(log_receiver_baton): New struct member invoke_diff_cmd.
(svn_cl__log): Ensure mutual exclusions between invoke_diff_cmd and
diff-cmd. Require 'diff' option to be set when requesting
invoke_diff option. Populate log_receiver_baton member
invoke_diff_cmd.
* subversion/svnlook/svnlook.c
(enum): New variable svnlook__invoke_diff_cmd.
(options_table[]): New entry 'invoke-diff-cmd'.
(cmd_tablcmd[]): Add svnlook__invoke_diff_cmd to diff cmd table
entry.
(svnlook_opt_state): New member variable "invoke_diff_cmd". Adjust
comment alignment on diff-cmd.
(svnlook_ctxt_t): New member variable "invoke_diff_cmd".
(print_diff_tree): Modify 'if condition' to include new
invoke_diff_cmd. Add conditional call to
/include/svn_io.c:svn_io_run_external_diff().
(get_ctxt_baton): Assign invoke_diff_cmd data.
(main): Add case svnlook__invoke_diff_cmd. Assign opt_arg to
opt_state.invoke_diff_cmd. Add exclusiveness test for
invoke_diff_cmd and diff_cmd.
* subversion/svn/svn.c
(svn_cl__longopt_t): New enum opt_invoke_diff_cmd.
(svn_cl__options "invoke-diff-cmd"): Add help information. Add new
variable 'opt_invoke_diff_cmd'.
(svn_cl__cmd_table[]): New option: 'invoke-diff-cmd', help info. Add
opt_invoke_diff_cmd to svn_cl__log entry. Add opt_invoke_diff_cmd
to the list of valid subcommands.
(sub_main): Add case opt_invoke_diff_cmd. Prohibit simultaneous
usage of --invoke-diff-cmd and --internal-diff. Prohibit
simultaneous usage of --diff-cmd and --invoke-diff-cmd. 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'.
* subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
(--invoke-diff-cmd): Add new entry to 'help' output data.
]]]
Index: subversion/include/private/svn_io_private.h
===================================================================
--- subversion/include/private/svn_io_private.h (revision 1602604)
+++ subversion/include/private/svn_io_private.h (working copy)
@@ -161,6 +161,51 @@
apr_pool_t *result_pool);
#endif /* WIN32 */
+/** Parse a user defined command to contain dynamically created labels
+ * and filenames. This function serves both diff and diff3 parsing
+ * requirements.
+ *
+ * When used in a diff context: (responding parse tokens in braces)
+ *
+ * @a label1 (%svn_label_old) refers to the label of @a tmpfile1
+ * (%svn_old) which is the pristine copy.
+ *
+ * @a label2 (%svn_label_new) refers to the label of @a tmpfile2
+ * (%svn_new) which is the altered copy.
+ *
+ * When used in a diff3 context:
+ *
+ * @a label1 refers to the label of @a tmpfile1 which is the 'mine'
+ * copy.
+ *
+ * @a label2 refers to the label of @a tmpfile2 which is the 'older'
+ * copy.
+ *
+ * @a label3 (%svn_label_base) refers to the label of @a base
+ * (%svn_base) which is the 'base' copy.
+ *
+ * In general:
+ *
+ * @a cmd is a user defined string containing 0 or more parse tokens
+ * which are expanded by the required labels and filenames.
+ *
+ * @a pool is used for temporary allocations.
+ *
+ * @return A NULL-terminated character array.
+ *
+ * @since New in 1.9.
+ */
+const char **
+svn_io__create_custom_diff_cmd(const char *label1,
+ const char *label2,
+ const char *label3,
+ const char *from,
+ const char *to,
+ const char *base,
+ const char *cmd,
+ apr_pool_t *pool);
+
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h (revision 1602604)
+++ subversion/include/svn_client.h (working copy)
@@ -3055,6 +3055,11 @@
* 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
Index: subversion/include/svn_config.h
===================================================================
--- subversion/include/svn_config.h (revision 1602604)
+++ subversion/include/svn_config.h (working copy)
@@ -123,6 +123,8 @@
#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_error_codes.h
===================================================================
--- subversion/include/svn_error_codes.h (revision 1602604)
+++ subversion/include/svn_error_codes.h (working copy)
@@ -1219,6 +1219,11 @@
SVN_ERR_CLIENT_CATEGORY_START + 23,
"The operation is forbidden by the server")
+ /** @since New in 1.9 */
+ SVN_ERRDEF(SVN_ERR_CLIENT_DIFF_CMD,
+ SVN_ERR_CLIENT_CATEGORY_START + 24,
+ "More than one diff command defined")
+
/* misc errors */
SVN_ERRDEF(SVN_ERR_BASE,
Index: subversion/include/svn_io.h
===================================================================
--- subversion/include/svn_io.h (revision 1602604)
+++ subversion/include/svn_io.h (working copy)
@@ -2462,6 +2462,23 @@
/** @} */
+/** 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/diff.c (revision 1602604)
+++ subversion/libsvn_client/diff.c (working copy)
@@ -541,6 +541,9 @@
/* If non-null, the external diff command to invoke. */
const char *diff_cmd;
+ /* external custom diff command */
+ const char *invoke_diff_cmd;
+
/* This is allocated in this struct's pool or a higher-up pool. */
union {
/* If 'diff_cmd' is null, then this is the parsed options to
@@ -777,8 +780,12 @@
return SVN_NO_ERROR;
}
+ if (dwi->diff_cmd && dwi->invoke_diff_cmd)
+ return svn_error_create(SVN_ERR_CLIENT_DIFF_CMD, NULL,
+ _("diff-cmd and invoke-diff-cmd are "
+ "mutually exclusive."));
- if (dwi->diff_cmd)
+ if (dwi->diff_cmd || dwi->invoke_diff_cmd)
{
svn_stream_t *errstream = dwi->errstream;
apr_file_t *outfile;
@@ -810,7 +817,6 @@
SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
svn_io_file_del_on_pool_cleanup,
scratch_pool, scratch_pool));
-
errfile = svn_stream__aprfile(errstream);
if (errfile)
errfilename = NULL;
@@ -819,13 +825,24 @@
svn_io_file_del_on_pool_cleanup,
scratch_pool, scratch_pool));
- SVN_ERR(svn_io_run_diff2(".",
- dwi->options.for_external.argv,
- dwi->options.for_external.argc,
- label1, label2,
- tmpfile1, tmpfile2,
- &exitcode, outfile, errfile,
- dwi->diff_cmd, scratch_pool));
+ /* "." is a non-canonical path for the diff process's working directory. */
+ if (dwi->diff_cmd)
+ SVN_ERR(svn_io_run_diff2(".",
+ dwi->options.for_external.argv,
+ dwi->options.for_external.argc,
+ label1, label2,
+ tmpfile1, tmpfile2,
+ &exitcode, outfile, errfile,
+ dwi->diff_cmd, scratch_pool));
+ else
+ {
+ SVN_ERR(svn_io_run_external_diff(".",
+ label1, label2,
+ tmpfile1, tmpfile2,
+ &exitcode, outfile, errfile,
+ dwi->invoke_diff_cmd,
+ scratch_pool));
+ }
/* Now, open and copy our files to our output streams. */
if (outfilename)
@@ -2228,7 +2245,8 @@
{
const char *diff_cmd = NULL;
- /* See if there is a diff command and/or diff arguments. */
+ /* See if there is a diff-cmd command and/or diff arguments first on
+ the command line and then in .subversion/config */
if (config)
{
svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
@@ -2249,37 +2267,53 @@
options = apr_array_make(result_pool, 0, sizeof(const char *));
if (diff_cmd)
- SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd,
- result_pool));
- else
- dwi->diff_cmd = NULL;
-
- /* If there was a command, arrange options to pass to it. */
- if (dwi->diff_cmd)
{
- const char **argv = NULL;
- int argc = options->nelts;
- if (argc)
- {
- int i;
- argv = apr_palloc(result_pool, argc * sizeof(char *));
- for (i = 0; i < argc; i++)
- SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
- APR_ARRAY_IDX(options, i, const char *), result_pool));
- }
- dwi->options.for_external.argv = argv;
- dwi->options.for_external.argc = argc;
+ SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd,
+ result_pool));
+ /* If there was a command, arrange options to pass to it. */
+ {
+ const char **argv = NULL;
+ int argc = options->nelts;
+ if (argc)
+ {
+ int i;
+ argv = apr_palloc(result_pool, argc * sizeof(char *));
+ for (i = 0; i < argc; i++)
+ SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
+ APR_ARRAY_IDX(options, i,
+ const char *),
+ result_pool));
+ }
+ dwi->options.for_external.argv = argv;
+ dwi->options.for_external.argc = argc;
+ }
}
- else /* No command, so arrange options for internal invocation instead. */
+ else
+ /* See if there is a diff-cmd command and/or diff arguments first on
+ the command line and then in .subversion/config */
{
- dwi->options.for_internal = svn_diff_file_options_create(result_pool);
- SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal,
- options, result_pool));
+ svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
+ 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(&dwi->invoke_diff_cmd,
+ diff_cmd,
+ result_pool));
+ }
+ else
+ {
+ /* No command, so arrange options for internal invocation instead. */
+ dwi->invoke_diff_cmd = NULL;
+ dwi->diff_cmd = NULL;
+ dwi->options.for_internal = svn_diff_file_options_create(result_pool);
+ SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal,
+ options, result_pool));
+ }
}
-
return SVN_NO_ERROR;
}
-
/*----------------------------------------------------------------------- */
/*** Public Interfaces. ***/
Index: subversion/libsvn_subr/config_file.c
===================================================================
--- subversion/libsvn_subr/config_file.c (revision 1602604)
+++ subversion/libsvn_subr/config_file.c (working copy)
@@ -1218,6 +1218,11 @@
"### 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 = (see svn help diff 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 1602604)
+++ subversion/libsvn_subr/io.c (working copy)
@@ -3004,8 +3004,166 @@
return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool);
}
+const char **
+svn_io__create_custom_diff_cmd(const char *label1,
+ const char *label2,
+ const char *label3,
+ const char *from,
+ const char *to,
+ const char *base,
+ const char *cmd,
+ apr_pool_t *pool)
+{
+ /*
+ This function can be tested with:
+ /subversion/tests/cmdline/diff_tests.py diff_invoke_external_diffcmd
+ */
+ apr_array_header_t *words;
+ const char ** result;
+ size_t argv, item, i, delimiters = 6;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ struct replace_tokens_tab
+ {
+ const char *delimiter;
+ const char *replace;
+ } tokens_tab[] = { /* Diff terminology */
+ { "%svn_label_old", label1 },
+ { "%svn_label_new", label2 },
+ { "%svn_label_base", label3 },
+ { "%svn_old", from },
+ { "%svn_new", to },
+ { "%svn_base", base },
+ { NULL, NULL }
+ };
+
+ if (label3) /* Merge terminology */
+ {
+ tokens_tab[0].delimiter = "%svn_label_mine";
+ tokens_tab[1].delimiter = "%svn_label_yours";
+ tokens_tab[3].delimiter = "%svn_mine";
+ tokens_tab[4].delimiter = "%svn_yours";
+ }
+
+ words = svn_cstring_split(cmd, " ", TRUE, scratch_pool);
+
+ result = apr_palloc(pool,
+ (words->nelts+1) * words->elt_size * sizeof(char *) );
+
+ for (item = 0, argv = 0; item < words->nelts; argv++, item++)
+ {
+ svn_stringbuf_t *word;
+
+ word = svn_stringbuf_create_empty(scratch_pool);
+ svn_stringbuf_appendcstr(word, APR_ARRAY_IDX(words, item, char *));
+
+ if ( (word->data[0] == '"') && (word->data[word->len-1] != '"') )
+ {
+ svn_stringbuf_t * complete = svn_stringbuf_create_empty(scratch_pool);
+ int done = 0;
+
+ while( (!done) && item < words->nelts)
+ {
+ svn_stringbuf_appendcstr(complete,
+ APR_ARRAY_IDX(words, item, char *));
+
+ if ( (complete->data[complete->len-1] == '"')
+ || (item == words->nelts - 1) )
+ {
+ word->data = complete->data;
+ done = 1;
+ }
+ else
+ {
+ svn_stringbuf_appendcstr(complete, " ");
+ item++;
+ }
+ }
+ }
+ i = 0;
+ while (i < delimiters)
+ {
+ char *found = strstr(word->data, tokens_tab[i].delimiter);
+
+ if (!found)
+ {
+ i++;
+ continue;
+ }
+
+ svn_stringbuf_replace(word, found - word->data,
+ strlen(tokens_tab[i].delimiter),
+ tokens_tab[i].replace,
+ strlen(tokens_tab[i].replace));
+ }
+ result[argv] = apr_pstrdup(pool,word->data);
+ }
+ result[argv] = NULL;
+ svn_pool_destroy(scratch_pool);
+ return result;
+}
+
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;
+
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ if (0 == strlen(external_diff_cmd))
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, NULL);
+
+ cmd = svn_io__create_custom_diff_cmd(label1, label2, NULL,
+ tmpfile1, tmpfile2, NULL,
+ external_diff_cmd, scratch_pool);
+ if (pexitcode == NULL)
+ pexitcode = &exitcode;
+
+ SVN_ERR(svn_io_run_cmd(dir, cmd[0], cmd, pexitcode, NULL, TRUE,
+ NULL, outfile, errfile, scratch_pool));
+
+ /* The man page for (GNU) diff describes the return value as:
+
+ "An exit status of 0 means no differences were found, 1 means
+ some differences were found, and 2 means trouble."
+
+ A return value of 2 typically occurs when diff cannot read its input
+ or write to its output, but in any case we probably ought to return an
+ error for anything other than 0 or 1 as the output is likely to be
+ corrupt.
+ */
+ if (*pexitcode != 0 && *pexitcode != 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"),
+ external_diff_cmd,
+ failed_command,
+ *pexitcode);
+ }
+ else
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_io_run_diff2(const char *dir,
const char *const *user_args,
int num_user_args,
Index: subversion/svn/cl-log.h
===================================================================
--- subversion/svn/cl-log.h (revision 1602604)
+++ subversion/svn/cl-log.h (working copy)
@@ -60,6 +60,9 @@
/* Depth applied to diff output. */
svn_depth_t depth;
+ /* invoke-diff-cmd arguments received from command line. */
+ const char *invoke_diff_cmd;
+
/* Diff arguments received from command line. */
const char *diff_extensions;
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h (revision 1602604)
+++ subversion/svn/cl.h (working copy)
@@ -184,6 +184,8 @@
{
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/log-cmd.c
===================================================================
--- subversion/svn/log-cmd.c (revision 1602604)
+++ subversion/svn/log-cmd.c (working copy)
@@ -704,6 +707,10 @@
"XML mode"));
}
+ if (opt_state->diff.diff_cmd && opt_state->diff.diff_cmd)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'diff-cmd' and 'invoke-diff-cmd' options are "
+ "mutually exclusive"));
if (opt_state->quiet && opt_state->show_diff)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'quiet' and 'diff' options are "
@@ -712,6 +719,10 @@
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'diff-cmd' option requires 'diff' "
"option"));
+ if (opt_state->diff.invoke_diff_cmd && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'invoke-diff-cmd' option requires 'diff' "
+ "option"));
if (opt_state->diff.internal_diff && (! opt_state->show_diff))
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'internal-diff' option requires "
Index: subversion/svn/svn.c
===================================================================
--- subversion/svn/svn.c (revision 1602604)
+++ subversion/svn/svn.c (working copy)
@@ -85,6 +85,7 @@
opt_ignore_properties,
opt_properties_only,
opt_patch_compatible,
+ opt_invoke_diff_cmd,
/* end of diff options */
opt_dry_run,
opt_editor_cmd,
@@ -347,6 +348,34 @@
{"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"
+ " "
+ "The following reserved keywords are replaced:\n"
+ " "
+ " %svn_new -- new file\n"
+ " "
+ " %svn_old -- old file\n"
+ " "
+ " %svn_label_new -- label of the new file\n"
+ " "
+ " %svn_label_old -- label of the old file\n"
+ " "
+ "Examples:\n"
+ " "
+ "--invoke-diff-cmd=\'diff -y %svn_new %svn_old\'\n"
+ " "
+ "--invoke-diff-cmd=\"kdiff3 -auto -o /home/u/log \\\n"
+ " "
+ " %svn_new %svn_old --L1 %svn_label_new \\\n"
+ " "
+ " --L2 \"Custom Label\" \'\n"
+ " "
+ "Reserved keywords may be embedded in strings:\n"
+ " "
+ "+%svn_new %svn_new- and file=%svn_label_new+")},
{"internal-diff", opt_internal_diff, 0,
N_("override diff-cmd specified in config file")},
{"no-diff-added", opt_no_diff_added, 0,
@@ -640,7 +669,8 @@
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"
@@ -805,7 +835,7 @@
{'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy, opt_incremental,
opt_xml, 'l', opt_with_all_revprops, opt_with_no_revprops,
opt_with_revprop, opt_auto_moves, opt_depth, opt_diff, opt_diff_cmd,
- opt_internal_diff, 'x', opt_search, opt_search_and },
+ opt_internal_diff, 'x', opt_invoke_diff_cmd, opt_search, opt_search_and },
{{opt_with_revprop, N_("retrieve revision property ARG")},
{'c', N_("the change made in revision ARG")}} },
@@ -2155,6 +2185,9 @@
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;
@@ -2559,7 +2592,7 @@
"--non-interactive"));
}
- /* Disallow simultaneous use of both --diff-cmd and
+ /* Disallow simultaneous use of --diff-cmd, --invoke-diff-cmd and
--internal-diff. */
if (opt_state.diff.diff_cmd && opt_state.diff.internal_diff)
{
@@ -2568,6 +2601,20 @@
"are mutually exclusive"));
}
+ if (opt_state.diff.invoke_diff_cmd && opt_state.diff.internal_diff)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--invoke-diff-cmd and --internal-diff "
+ "are mutually exclusive"));
+ }
+
+ if ((opt_state.diff.diff_cmd) && (opt_state.diff.invoke_diff_cmd))
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--invoke-diff-cmd and --diff-cmd "
+ "are mutually exclusive"));
+ }
+
/* 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)
@@ -2778,6 +2825,9 @@
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);
+ 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/svnlook/svnlook.c
===================================================================
--- subversion/svnlook/svnlook.c (revision 1602604)
+++ subversion/svnlook/svnlook.c (working copy)
@@ -102,6 +102,7 @@
svnlook__ignore_properties,
svnlook__properties_only,
svnlook__diff_cmd,
+ svnlook__invoke_diff_cmd,
svnlook__show_inherited_props,
svnlook__no_newline
};
@@ -138,6 +139,9 @@
{"diff-cmd", svnlook__diff_cmd, 1,
N_("use ARG as diff command")},
+ {"invoke-diff-cmd", svnlook__invoke_diff_cmd, 1,
+ N_("Customizable diff command (see svn help diff)")},
+
{"ignore-properties", svnlook__ignore_properties, 0,
N_("ignore properties during the operation")},
@@ -230,7 +234,8 @@
"Print GNU-style diffs of changed files and properties.\n"),
{'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
- svnlook__ignore_properties, svnlook__properties_only} },
+ svnlook__ignore_properties, svnlook__properties_only,
+ svnlook__invoke_diff_cmd} },
{"dirs-changed", subcommand_dirschanged, {0},
N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
@@ -335,7 +340,8 @@
svn_boolean_t quiet; /* --quiet */
svn_boolean_t ignore_properties; /* --ignore_properties */
svn_boolean_t properties_only; /* --properties-only */
- const char *diff_cmd; /* --diff-cmd */
+ const char *diff_cmd; /* --diff-cmd */
+ const char *invoke_diff_cmd; /* --invoke-diff-cmd */
svn_boolean_t show_inherited_props; /* --show-inherited-props */
svn_boolean_t no_newline; /* --no-newline */
};
@@ -360,6 +366,7 @@
svn_boolean_t ignore_properties;
svn_boolean_t properties_only;
const char *diff_cmd;
+ const char *invoke_diff_cmd;
} svnlook_ctxt_t;
@@ -951,7 +958,7 @@
}
else
{
- if (c->diff_cmd)
+ if (c->diff_cmd || c->invoke_diff_cmd)
{
apr_file_t *outfile;
apr_file_t *errfile;
@@ -1006,14 +1013,31 @@
SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
svn_io_file_del_on_pool_cleanup, pool, pool));
- SVN_ERR(svn_io_run_diff2(".",
- diff_cmd_argv,
- diff_cmd_argc,
- orig_label, new_label,
- orig_path, new_path,
- &exitcode, outfile, errfile,
- c->diff_cmd, pool));
+ if (c->diff_cmd)
+ SVN_ERR(svn_io_run_diff2(".",
+ diff_cmd_argv,
+ diff_cmd_argc,
+ orig_label, new_label,
+ orig_path, new_path,
+ &exitcode, outfile, errfile,
+ c->diff_cmd, pool));
+ else if (c->invoke_diff_cmd)
+ SVN_ERR(svn_io_run_external_diff(".",
+ orig_label,
+ new_label,
+ orig_path,
+ new_path,
+ &exitcode,
+ outfile,
+ errfile,
+ c->invoke_diff_cmd,
+ pool));
+
+ SVN_ERR(svn_io_file_close(outfile, pool));
+ SVN_ERR(svn_io_file_close(errfile, pool));
+
+
/* Now, open and copy our files to our output streams. */
if (outfilename)
{
@@ -2100,6 +2124,7 @@
" \t\n\r", TRUE, pool);
baton->ignore_properties = opt_state->ignore_properties;
baton->properties_only = opt_state->properties_only;
+ baton->invoke_diff_cmd = opt_state->invoke_diff_cmd;
baton->diff_cmd = opt_state->diff_cmd;
if (baton->txn_name)
@@ -2514,7 +2539,6 @@
_("Invalid revision number supplied"));
}
break;
-
case 't':
opt_state.txn = opt_arg;
break;
@@ -2605,6 +2629,10 @@
opt_state.diff_cmd = opt_arg;
break;
+ case svnlook__invoke_diff_cmd:
+ opt_state.invoke_diff_cmd = opt_arg;
+ break;
+
case svnlook__show_inherited_props:
opt_state.show_inherited_props = TRUE;
break;
@@ -2635,6 +2663,13 @@
_("Cannot use the '--show-inherited-props' option with the "
"'--revprop' option"));
+ /* The --diff-cmd and --invoke-diff-cmd options may not co-exist. */
+ if (opt_state.diff_cmd && opt_state.invoke_diff_cmd)
+ SVN_INT_ERR(svn_error_create
+ (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("Cannot use the '--diff-cmd' option with the "
+ "'--invoke-diff-cmd' option")));
+
/* If the user asked for help, then the rest of the arguments are
the names of subcommands to get help on (if any), or else they're
just typos/mistakes. Whatever the case, the subcommand to
Index: subversion/tests/cmdline/diff_tests.py
===================================================================
--- subversion/tests/cmdline/diff_tests.py (revision 1602604)
+++ subversion/tests/cmdline/diff_tests.py (working copy)
@@ -3065,6 +3065,7 @@
svntest.actions.run_and_verify_svn(None, [], err.INVALID_DIFF_OPTION,
'diff', '-x', sbox.wc_dir, '-r', '1')
+#----------------------------------------------------------------------
# Check the order of the arguments for an external diff tool
def diff_external_diffcmd(sbox):
"svn diff --diff-cmd provides the correct arguments"
@@ -3102,7 +3103,54 @@
'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 --invoke-diff-cmd passes correct args"
+ diff_script_path = os.path.abspath(".")+"/diff"
+
+ svntest.main.create_python_hook_script(diff_script_path, 'import sys\n'
+ 'for arg in sys.argv[1:]:\n print(arg)\n')
+
+ if sys.platform == 'win32':
+ diff_script_path = "%s.bat" % diff_script_path
+
+ 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",
+ # correct label %svn_label_old -> label 1
+ "iota (revision 1)\n",
+
+ # correct file %svn_old -> old
+ os.path.abspath(svntest.wc.text_base_path("iota")) + "\n",
+
+ # correct label %svn_label_new -> label 2
+ "iota (working copy)\n",
+
+ # correct file %svn_new -> new
+ os.path.abspath("iota") + "\n",
+
+ # preservation of quoted string "X Y Z"-> "X Y Z"
+ "\"X Y Z\"\n",
+
+ # correct insertion of filename into string "+%svn_new+" -> "+"+new+"+"
+ "+" + os.path.abspath("iota") + "+\n",
+
+ ])
+
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'diff',
+ '--invoke-diff-cmd='+diff_script_path+
+ ' %svn_label_old %svn_old %svn_label_new %svn_new \"X Y Z\" +%svn_new+',
+ iota_path)
+
+
#----------------------------------------------------------------------
# Diffing an unrelated repository URL against working copy with
# local modifications (i.e. not committed). This is issue #3295 (diff
@@ -4730,6 +4778,7 @@
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: subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
===================================================================
--- subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout (revision 1602604)
+++ subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout (working copy)
@@ -111,6 +111,20 @@
-w, --ignore-all-space: Ignore all white space
--ignore-eol-style: Ignore changes in EOL style
-p, --show-c-function: Show C function name
+ --invoke-diff-cmd ARG : use ARG as format string for external diff command
+ invocation.
+ The following reserved keywords are replaced:
+ %svn_new -- new file
+ %svn_old -- old file
+ %svn_label_new -- label of the new file
+ %svn_label_old -- label of the old file
+ Examples:
+ --invoke-diff-cmd='diff -y %svn_new %svn_old'
+ --invoke-diff-cmd="kdiff3 -auto -o /home/u/log \
+ %svn_new %svn_old --L1 %svn_label_new \
+ --L2 "Custom Label" '
+ Reserved keywords may be embedded in strings:
+ +%svn_new %svn_new- and file=%svn_label_new+
--search ARG : use ARG as search pattern (glob syntax)
--search-and ARG : combine ARG with the previous search pattern