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
 

Reply via email to