[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] Issue #1093: make diff follow history

From: Josh Pieper <jjp_at_pobox.com>
Date: 2004-05-08 16:59:25 CEST

Josh Pieper wrote:
> 1. diff [-r M[:N]] [TARGET[@REV]...]
>
> 2. diff --old=OLD-TGT[@OLDREV] --new=NEW-TGT[@NEWREV] [PATH...]
>
> 3. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]
>
> Is this syntax agreeable to everyone? Are there any threads or
> important points that I've missed?

Well, due to the resounding lack of response, I am assuming that there
is at least some partial agreement that this syntax is workable and
have started a patch to implement it. Since the patch does change the
syntax and isn't easily broken up, I'm posting a work-in-progress
here.

The patch covers nearly all of the new syntax except the case of
comparing a repository URL to a working copy. That code used the
update editor to generate a diff and I am not sure how best to rework
it to handle pegged diffs. I am open to suggestions on how to proceed
on that front.

-Josh

--------------------
Issue 1093: Change the syntax for 'svn diff' and allow it to operate
on pegged paths, thus enabling copy history to be traversed. The new
syntax has two forms and a third shortcut format.

  1. diff [-r N[:M]] [TARGET[@REV]...]
  2. diff --old=OLD-TGT[@OLDREV] --new=NEW-TGT[@NEWREV] [PATH...]
  3. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]

Where case 1 performs pegged diffs, and case 3 is a shortcut for case
2 when no PATHs are specified and both targets are URLs. It should be
noted that only case 1 will accept the -r option now, to specify a
revision with case 2 or 3, use the @REV syntax.

* subversion/include/client.h
* subversion/libsvn_client/diff.c
  (diff_repos_repos, diff_repos_wc): Accept a peg revision option that
    will cause the actual paths to be diffed to be selected by
    following copy history.
  (do_diff): Let the helper functions know this is not a pegged diff.
  (do_diff_peg): New, implements the logic of svn_client_diff_peg.
  (svn_client_diff_peg): New, performs a pegged diff on a filesystem
    object between two revisions.

* subversion/clients/cmdline/diff-cmd.c
  (parse_path): New, copied from libsvn_subr/opt.c, parses out the
    optional @REV from the end of paths.
  (svn_cl__diff): Implement the new syntax.

* subversion/clients/cmdline/main.c
  (svn_cl__cmd_table): Add new help text for 'svn diff'.

Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h (revision 9649)
+++ subversion/include/svn_client.h (working copy)
@@ -803,6 +803,28 @@
                               apr_pool_t *pool);
 
 
+/** Produce diff output which describes the delta between the
+ * filesystem object @a path in peg revision @a peg_revision, as it
+ * changed between @a revision1 and @a revision2. Print the output of
+ * the diff to @a outfile, and any errors to @a errfile. @a path can
+ * be either a working-copy path or URL.
+ *
+ * All other options are handled identically to svn_client_diff.
+ */
+svn_error_t *svn_client_diff_peg (const apr_array_header_t *diff_options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
 /** Merge changes from @a source1/@a revision1 to @a source2/@a revision2 into
  * the working-copy path @a target_wcpath.
  *
Index: subversion/libsvn_client/diff.c
===================================================================
--- subversion/libsvn_client/diff.c (revision 9649)
+++ subversion/libsvn_client/diff.c (working copy)
@@ -1532,7 +1532,10 @@
 /* Perform a diff between two repository paths.
    
    PATH1 and PATH2 may be either URLs or the working copy paths.
- REVISION1 and REVISION2 are their respective revisions.
+ REVISION1 and REVISION2 are their respective revisions. If
+ PEG_REVISION is specified, PATH2 is the path at the peg revision,
+ and the actual two paths compared are determined by following copy
+ history from PATH2.
 
    All other options are the same as those passed to svn_client_diff(). */
 static svn_error_t *
@@ -1541,6 +1544,7 @@
                   const svn_opt_revision_t *revision1,
                   const char *path2,
                   const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
                   svn_boolean_t recurse,
                   svn_boolean_t ignore_ancestry,
                   const svn_wc_diff_callbacks_t *callbacks,
@@ -1579,6 +1583,27 @@
   SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
   SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, url1, pool));
 
+ /* If we are performing a pegged diff, we need to find out what our
+ actual URLs will be. */
+ if (peg_revision->kind != svn_opt_revision_unspecified)
+ {
+ void *sessionpeg;
+ svn_opt_revision_t *start_ignore, *end_ignore;
+
+ /* Open an RA session to the peg URL. */
+ SVN_ERR (svn_client__open_ra_session (&sessionpeg, ra_lib, url2, NULL,
+ NULL, NULL, FALSE, TRUE,
+ ctx, temppool));
+
+ SVN_ERR (svn_client__repos_locations (&url1, &start_ignore,
+ &url2, &end_ignore,
+ path2,
+ peg_revision,
+ revision1, revision2,
+ ra_lib, sessionpeg,
+ ctx, pool));
+ }
+
   /* Open temporary RA sessions to each URL. */
   SVN_ERR (svn_client__open_ra_session (&session1, ra_lib, url1, NULL,
                                         NULL, NULL, FALSE, TRUE,
@@ -1678,12 +1703,16 @@
    PATH1 may be either a URL or a working copy path. PATH2 is a
    working copy path. REVISION1 and REVISION2 are their respective
    revisions. If REVERSE is TRUE, the diff will be done in reverse.
+ If PEG_REVISION is specified, then PATH1 is the path in the peg
+ 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_diff(). */
 static svn_error_t *
 diff_repos_wc (const apr_array_header_t *options,
                const char *path1,
                const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *peg_revision,
                const char *path2,
                const svn_opt_revision_t *revision2,
                svn_boolean_t reverse,
@@ -1713,26 +1742,52 @@
   /* Figure out URL1. */
   SVN_ERR (convert_to_url (&url1, path1, pool));
 
+ /* Establish RA session to URL1's anchor */
+ SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
+ SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, url1, pool));
+
+ /* If we are performing a pegged diff, we need to find out what our
+ actual URL will be. */
+ if (peg_revision->kind != svn_opt_revision_unspecified)
+ {
+ void *sessionpeg;
+ svn_opt_revision_t *start_ignore, *end_ignore, end;
+ const char *url_ignore;
+
+ end.kind = svn_opt_revision_unspecified;
+
+ /* Open an RA session to the peg URL. */
+ SVN_ERR (svn_client__open_ra_session (&sessionpeg, ra_lib, url1, NULL,
+ NULL, NULL, FALSE, TRUE,
+ ctx, pool));
+
+ SVN_ERR (svn_client__repos_locations (&url1, &start_ignore,
+ &url_ignore, &end_ignore,
+ path1,
+ peg_revision,
+ revision1, &end,
+ ra_lib, sessionpeg,
+ ctx, pool));
+ }
+
   /* Possibly split up PATH2 into anchor/target. If we do so, then we
      must split URL1 as well. */
   anchor1 = url1;
   anchor2 = path2;
   target1 = "";
   target2 = "";
+
   SVN_ERR (svn_io_check_path (path2, &kind, pool));
   if (kind == svn_node_file)
     {
       svn_path_split (path2, &anchor2, &target2, pool);
       svn_path_split (url1, &anchor1, &target1, pool);
     }
-
- /* Establish RA session to URL1's anchor */
- SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
- SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, anchor1, pool));
+
   SVN_ERR (svn_client__open_ra_session (&session, ra_lib, anchor1,
                                         NULL, NULL, NULL, FALSE, TRUE,
                                         ctx, pool));
-
+
   /* Set up diff editor according to path2's anchor/target. */
   SVN_ERR (svn_wc_adm_open2 (&adm_access, NULL, anchor2, FALSE,
                              (recurse && (! *target2)) ? -1 : 0, pool));
@@ -1805,7 +1860,11 @@
 {
   svn_boolean_t is_local_rev1, is_local_rev2;
   svn_boolean_t is_repos_path1, is_repos_path2;
+ svn_opt_revision_t peg_revision;
 
+ /* We will never do a pegged diff from here. */
+ peg_revision.kind = svn_opt_revision_unspecified;
+
   /* Either path could be a URL or a working copy path. Let's figure
      out what's what. */
   is_repos_path1 = svn_path_is_url (path1);
@@ -1838,13 +1897,15 @@
       if (is_repos_path2) /* path2 is (effectively) a URL */
         {
           SVN_ERR (diff_repos_repos (options, path1, revision1, path2,
- revision2, recurse, ignore_ancestry,
- callbacks, callback_baton, ctx, pool));
+ revision2, &peg_revision, recurse,
+ ignore_ancestry, callbacks,
+ callback_baton, ctx, pool));
         }
       else /* path2 is a working copy path */
         {
- SVN_ERR (diff_repos_wc (options, path1, revision1, path2, revision2,
- FALSE, recurse, ignore_ancestry, callbacks,
+ SVN_ERR (diff_repos_wc (options, path1, revision1, &peg_revision,
+ path2, revision2, FALSE, recurse,
+ ignore_ancestry, callbacks,
                                   callback_baton, ctx, pool));
         }
     }
@@ -1852,8 +1913,9 @@
     {
       if (is_repos_path2) /* path2 is (effectively) a URL */
         {
- SVN_ERR (diff_repos_wc (options, path2, revision2, path1, revision1,
- TRUE, recurse, ignore_ancestry, callbacks,
+ SVN_ERR (diff_repos_wc (options, path2, revision2, &peg_revision,
+ path1, revision1, TRUE, recurse,
+ ignore_ancestry, callbacks,
                                   callback_baton, ctx, pool));
         }
       else /* path2 is a working copy path */
@@ -1866,9 +1928,81 @@
   return SVN_NO_ERROR;
 }
 
+/* This is basically just the guts of svn_client_diff_peg(). */
+static svn_error_t *
+do_diff_peg (const apr_array_header_t *options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const svn_wc_diff_callbacks_t *callbacks,
+ struct diff_cmd_baton *callback_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_local_rev1, is_local_rev2;
+ svn_boolean_t is_repos_path;
 
+ /* Either path could be a URL or a working copy path. Let's figure
+ out what's what. */
+ is_repos_path = svn_path_is_url (path);
 
+ /* Verify our revision arguments in light of the paths. */
+ if ((revision1->kind == svn_opt_revision_unspecified)
+ || (revision2->kind == svn_opt_revision_unspecified))
+ return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Not all required revisions are specified"));
 
+ /* Revisions can be said to be local or remote. BASE and WORKING,
+ for example, are local. */
+ is_local_rev1 = ((revision1->kind == svn_opt_revision_base)
+ || (revision1->kind == svn_opt_revision_working));
+ is_local_rev2 = ((revision2->kind == svn_opt_revision_base)
+ || (revision2->kind == svn_opt_revision_working));
+
+ if (is_local_rev1 && is_local_rev2)
+ return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("At least one revision must be non-local for "
+ "a pegged diff."));
+
+ if (! is_local_rev1) /* path1 is (effectively) a URL */
+ {
+ if (! is_local_rev2) /* path2 is (effectively) a URL */
+ {
+ SVN_ERR (diff_repos_repos (options, path, revision1, path,
+ revision2, peg_revision, recurse,
+ ignore_ancestry, callbacks,
+ callback_baton, ctx, pool));
+ }
+ else /* path2 is a working copy path */
+ {
+ SVN_ERR (diff_repos_wc (options, path, revision1, peg_revision, path,
+ revision2, FALSE, recurse, ignore_ancestry,
+ callbacks, callback_baton, ctx, pool));
+ }
+ }
+ else /* path1 is a working copy path */
+ {
+ if (! is_local_rev2) /* path2 is (effectively) a URL */
+ {
+ SVN_ERR (diff_repos_wc (options, path, revision2, peg_revision,
+ path, revision1, TRUE, recurse,
+ ignore_ancestry, callbacks,
+ callback_baton, ctx, pool));
+ }
+ else
+ {
+ SVN_ERR (diff_wc_wc (options, path, revision1, path, revision2,
+ recurse, callbacks, callback_baton, ctx, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
 /*----------------------------------------------------------------------- */
 
 /*** Public Interfaces. ***/
@@ -1963,6 +2097,54 @@
 
 
 svn_error_t *
+svn_client_diff_peg (const apr_array_header_t *options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct diff_cmd_baton diff_cmd_baton;
+ svn_wc_diff_callbacks_t diff_callbacks;
+
+ diff_callbacks.file_changed = diff_file_changed;
+ diff_callbacks.file_added = diff_file_added;
+ diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
+ diff_file_deleted_with_diff;
+ diff_callbacks.dir_added = diff_dir_added;
+ diff_callbacks.dir_deleted = diff_dir_deleted;
+ diff_callbacks.props_changed = diff_props_changed;
+
+ diff_cmd_baton.orig_path_1 = path;
+ diff_cmd_baton.orig_path_2 = path;
+
+ diff_cmd_baton.options = options;
+ diff_cmd_baton.pool = pool;
+ diff_cmd_baton.outfile = outfile;
+ diff_cmd_baton.errfile = errfile;
+ diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
+ diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
+
+ diff_cmd_baton.config = ctx->config;
+
+ return do_diff_peg (options,
+ path, peg_revision,
+ start_revision, end_revision,
+ recurse,
+ ignore_ancestry,
+ &diff_callbacks, &diff_cmd_baton,
+ ctx,
+ pool);
+}
+
+
+svn_error_t *
 svn_client_merge (const char *source1,
                   const svn_opt_revision_t *revision1,
                   const char *source2,
Index: subversion/clients/cmdline/diff-cmd.c
===================================================================
--- subversion/clients/cmdline/diff-cmd.c (revision 9649)
+++ subversion/clients/cmdline/diff-cmd.c (working copy)
@@ -30,6 +30,7 @@
 #include "svn_delta.h"
 #include "svn_error.h"
 #include "svn_types.h"
+#include "svn_utf.h"
 #include "cl.h"
 
 #include "svn_private_config.h"
@@ -37,6 +38,67 @@
 
 /*** Code. ***/
 
+/* Parse a working-copy or url PATH, looking for an "@" sign, e.g.
+
+ foo/bar/baz@13
+ http://blah/bloo@27
+ blarg/snarf@HEAD
+
+ If an "@" is found, return the two halves in *TRUEPATH and *REV,
+ allocating in POOL.
+
+ If no "@" is found, set *TRUEPATH to PATH and *REV to kind 'unspecified'.
+*/
+static svn_error_t *
+parse_path (svn_opt_revision_t *rev,
+ const char **truepath,
+ const char *path /* UTF-8! */,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *subpool = svn_pool_create (pool);
+ svn_opt_revision_t start_revision, end_revision;
+
+ /* scanning from right to left, just to be friendly to any
+ screwed-up filenames that might *actually* contain @-signs. :-) */
+ for (i = (strlen (path) - 1); i >= 0; i--)
+ {
+ /* If we hit a path separator, stop looking. */
+ if (path[i] == '/')
+ break;
+
+ if (path[i] == '@')
+ {
+ const char *native_rev;
+
+ SVN_ERR (svn_utf_cstring_from_utf8 (&native_rev, path + i + 1,
+ subpool));
+
+ if (svn_opt_parse_revision (&start_revision,
+ &end_revision,
+ native_rev, subpool))
+ return svn_error_createf (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error parsing revision '%s'"),
+ path + i + 1);
+
+ *truepath = apr_pstrndup (pool, path, i);
+ rev->kind = start_revision.kind;
+ rev->value = start_revision.value;
+
+ svn_pool_destroy (subpool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Didn't find an @-sign. */
+ *truepath = path;
+ rev->kind = svn_opt_revision_unspecified;
+
+ svn_pool_destroy (subpool);
+ return SVN_NO_ERROR;
+}
+
+
 /* An svn_opt_subcommand_t to handle the 'diff' command.
    This implements the `svn_opt_subcommand_t' interface. */
 svn_error_t *
@@ -51,6 +113,7 @@
   apr_status_t status;
   const char *old_target, *new_target;
   apr_pool_t *subpool;
+ svn_boolean_t pegged_diff = FALSE;
   int i;
 
   /* Fall back to "" to get options initialized either way. */
@@ -67,36 +130,62 @@
     return svn_error_wrap_apr (status, _("Can't open stderr"));
 
   if (! opt_state->old_target && ! opt_state->new_target
- && opt_state->start_revision.kind != svn_opt_revision_unspecified
- && opt_state->end_revision.kind != svn_opt_revision_unspecified
- && (os->argc - os->ind == 1)
- && svn_path_is_url (os->argv[os->ind]))
+ && (os->argc - os->ind == 2)
+ && svn_path_is_url (os->argv[os->ind])
+ && svn_path_is_url (os->argv[os->ind + 1]))
     {
- /* The 'svn diff -rN:M URL' case (matches 'svn merge'). */
+ if (opt_state->start_revision.kind != svn_opt_revision_unspecified
+ || opt_state->end_revision.kind != svn_opt_revision_unspecified)
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("This form of diff cannot accept a -r argument."));
+
+ /* The 'svn diff OLD_URL[@OLDREV] NEW_URL[@NEWREV]' case matches. */
       SVN_ERR (svn_opt_args_to_target_array (&targets, os,
                                              opt_state->targets,
                                              &(opt_state->start_revision),
                                              &(opt_state->end_revision),
- FALSE, /* no @revs */ pool));
+ TRUE, /* extract @revs */ pool));
 
- old_target = new_target = APR_ARRAY_IDX(targets, 0, const char *);
+ old_target = APR_ARRAY_IDX (targets, 0, const char *);
+ new_target = APR_ARRAY_IDX (targets, 1, const char *);
       targets->nelts = 0;
+
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
+ opt_state->start_revision.kind = svn_opt_revision_head;
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = svn_opt_revision_head;
     }
- else if (! opt_state->old_target && ! opt_state->new_target
- && (os->argc - os->ind == 2)
- && svn_path_is_url (os->argv[os->ind])
- && svn_path_is_url (os->argv[os->ind + 1]))
+ else if (opt_state->old_target && opt_state->new_target)
     {
- /* The 'svn diff URL1[@N] URL2[@M]' case (matches 'svn merge'). */
+ apr_array_header_t *tmp, *tmp2;
+
+ if (opt_state->start_revision.kind != svn_opt_revision_unspecified
+ || opt_state->end_revision.kind != svn_opt_revision_unspecified)
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("This form of diff cannot accept a -r argument."));
+
+ /* The 'svn diff --old=OLD[@OLDREV] --new=NEW[@NEWREV]
+ [PATH...]' case matches. */
+
       SVN_ERR (svn_opt_args_to_target_array (&targets, os,
                                              opt_state->targets,
+ NULL,
+ NULL,
+ FALSE, pool));
+
+ tmp = apr_array_make (pool, 2, sizeof (const char *));
+ APR_ARRAY_PUSH (tmp, const char *) = (opt_state->old_target);
+ APR_ARRAY_PUSH (tmp, const char *) = (opt_state->new_target);
+
+ SVN_ERR (svn_opt_args_to_target_array (&tmp2, os, tmp,
                                              &(opt_state->start_revision),
                                              &(opt_state->end_revision),
                                              TRUE, /* extract @revs */ pool));
 
- old_target = APR_ARRAY_IDX(targets, 0, const char *);
- new_target = APR_ARRAY_IDX(targets, 1, const char *);
- targets->nelts = 0;
+ old_target = APR_ARRAY_IDX (tmp2, 0, const char *);
+ new_target = APR_ARRAY_IDX (tmp2, 1, const char *);
 
       if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
         opt_state->start_revision.kind = svn_opt_revision_head;
@@ -105,68 +194,109 @@
     }
   else
     {
- /* The 'svn diff [-rN[:M]] [--old OLD] [--new NEW] [PATH ...]' case */
- apr_array_header_t *tmp = apr_array_make (pool, 2, sizeof (const char *));
- apr_array_header_t *tmp2;
+ apr_array_header_t *tmp, *tmp2;
+ svn_boolean_t working_copy_present = FALSE;
+
+ /* The 'svn diff [-r M[:N]] [TARGET[@REV]...]' case matches. */
 
+ /* Here each target is a pegged object. Find out the starting
+ and ending paths for each target. */
       SVN_ERR (svn_opt_args_to_target_array (&targets, os,
                                              opt_state->targets,
- &(opt_state->start_revision),
- &(opt_state->end_revision),
- FALSE, /* no @revs */ pool));
+ NULL,
+ NULL,
+ FALSE, pool));
 
- APR_ARRAY_PUSH (tmp, const char *) = (opt_state->old_target
- ? opt_state->old_target : ".");
- APR_ARRAY_PUSH (tmp, const char *) = (opt_state->new_target
- ? opt_state->new_target
- : APR_ARRAY_IDX(tmp, 0,
- const char *));
+ svn_opt_push_implicit_dot_target (targets, pool);
 
+ tmp = apr_array_make (pool, 2, sizeof (const char *));
+ APR_ARRAY_PUSH (tmp, const char *) = ".";
+ APR_ARRAY_PUSH (tmp, const char *) = ".";
+
       SVN_ERR (svn_opt_args_to_target_array (&tmp2, os, tmp,
                                              &(opt_state->start_revision),
                                              &(opt_state->end_revision),
                                              TRUE, /* extract @revs */ pool));
 
- old_target = APR_ARRAY_IDX(tmp2, 0, const char *);
- new_target = APR_ARRAY_IDX(tmp2, 1, const char *);
+ old_target = APR_ARRAY_IDX (tmp2, 0, const char *);
+ new_target = APR_ARRAY_IDX (tmp2, 1, const char *);
 
- /* Default to HEAD for an URL, BASE otherwise */
- if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
- opt_state->start_revision.kind = (svn_path_is_url (old_target)
- ? svn_opt_revision_head
- : svn_opt_revision_base);
+ /* Check to see if at least one of our paths is a working copy
+ path. */
+ for (i = 0; i < targets->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX (targets, i, const char *);
+ if (! svn_path_is_url (path))
+ working_copy_present = TRUE;
+ }
+
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified
+ && working_copy_present)
+ opt_state->start_revision.kind = svn_opt_revision_base;
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = working_copy_present
+ ? svn_opt_revision_working : svn_opt_revision_head;
 
- /* Default to HEAD for an URL, WORKING otherwise */
- if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
- opt_state->end_revision.kind = (svn_path_is_url (new_target)
- ? svn_opt_revision_head
- : svn_opt_revision_working);
+ /* Determine if we need to do pegged diffs. */
+ if (opt_state->start_revision.kind != svn_opt_revision_base
+ && opt_state->start_revision.kind != svn_opt_revision_working)
+ pegged_diff = TRUE;
+
     }
 
- svn_opt_push_implicit_dot_target (targets, pool);
-
   subpool = svn_pool_create (pool);
   for (i = 0; i < targets->nelts; ++i)
     {
- const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ const char *path = APR_ARRAY_IDX (targets, i, const char *);
       const char *target1, *target2;
 
- svn_pool_clear (subpool);
- target1 = svn_path_join (old_target, path, subpool);
- target2 = svn_path_join (new_target, path, subpool);
+ if (! pegged_diff)
+ {
+ svn_pool_clear (subpool);
+ target1 = svn_path_join (old_target, path, subpool);
+ target2 = svn_path_join (new_target, path, subpool);
+
+ SVN_ERR (svn_client_diff (options,
+ target1,
+ &(opt_state->start_revision),
+ target2,
+ &(opt_state->end_revision),
+ opt_state->nonrecursive ? FALSE : TRUE,
+ opt_state->notice_ancestry ? FALSE : TRUE,
+ opt_state->no_diff_deleted,
+ outfile,
+ errfile,
+ ((svn_cl__cmd_baton_t *)baton)->ctx,
+ pool));
+ }
+ else
+ {
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ /* First check for a peg revision. */
+ SVN_ERR (parse_path (&peg_revision, &truepath, path, pool));
+
+ /* Set the default peg revision if one was not specified. */
+ if (peg_revision.kind == svn_opt_revision_unspecified)
+ peg_revision.kind = svn_path_is_url (path)
+ ? svn_opt_revision_head : svn_opt_revision_working;
 
- SVN_ERR (svn_client_diff (options,
- target1,
- &(opt_state->start_revision),
- target2,
- &(opt_state->end_revision),
- opt_state->nonrecursive ? FALSE : TRUE,
- opt_state->notice_ancestry ? FALSE : TRUE,
- opt_state->no_diff_deleted,
- outfile,
- errfile,
- ((svn_cl__cmd_baton_t *)baton)->ctx,
- pool));
+ SVN_ERR (svn_client_diff_peg (options,
+ path,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ opt_state->nonrecursive
+ ? FALSE : TRUE,
+ opt_state->notice_ancestry
+ ? FALSE : TRUE,
+ opt_state->no_diff_deleted,
+ outfile,
+ errfile,
+ ((svn_cl__cmd_baton_t *)baton)->ctx,
+ pool));
+ }
     }
   svn_pool_destroy (subpool);
 
Index: subversion/clients/cmdline/main.c
===================================================================
--- subversion/clients/cmdline/main.c (revision 9649)
+++ subversion/clients/cmdline/main.c (working copy)
@@ -237,32 +237,32 @@
   
   { "diff", svn_cl__diff, {"di"},
     N_("Display the differences between two paths.\n"
- "usage: 1. diff [-r N[:M]] [--old OLD-TGT] [--new NEW-TGT] [PATH...]\n"
- " 2. diff -r N:M URL\n"
- " 3. diff [-r N[:M]] URL1[@N] URL2[@M]\n"
+ "usage: 1. diff [-r N[:M]] [TARGET[@REV]...]\n"
+ " 2. diff --old=OLD-TGT[@OLDREV] --new=NEW-TGT[@NEWREV] [PATH...]\n"
+ " 3. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]\n"
        "\n"
- " 1. Display the differences between OLD-TGT and NEW-TGT. PATHs, if\n"
- " given, are relative to OLD-TGT and NEW-TGT and restrict the "
- "output\n"
- " to differences for those paths. OLD-TGT and NEW-TGT may "
- "be working\n"
- " copy paths or URL[@REV].\n"
+ " 1. Display the changes made to TARGETs as they are seen in REV "
+ "between\n"
+ " two revisions. TARGETs may working copy paths or URL[@REV].\n"
        "\n"
- " OLD-TGT defaults to the path '.' and NEW-TGT defaults to "
- "OLD-TGT.\n"
- " N defaults to BASE or, if OLD-TGT is an URL, to HEAD.\n"
- " M defaults to the current working version or, if NEW-TGT is "
- "an URL,\n"
- " to HEAD.\n"
+ " N defaults to BASE if any TARGET is a working copy path, otherwise "
+ "it\n"
+ " must be specified. M defaults to the current working version if "
+ "any\n"
+ " TARGET is a working copy path, otherwise it defaults to HEAD.\n"
        "\n"
- " '-r N' sets the revision of OLD-TGT to N, '-r N:M' also "
- "sets the\n"
- " revision of NEW-TGT to M.\n"
+ " 2. Display the differences between OLD-TGT as it was seen in OLDREV "
+ "and\n"
+ " NEW-TGT as it was seen in NEWREV. PATHs, if given, are relative "
+ "to\n"
+ " OLD-TGT and NEW-TGT and restrict the output to differences for "
+ "those\n"
+ " paths. OLD-TGT and NEW-TGT may be working copy paths or "
+ "URL[@REV].\n"
        "\n"
- " 2. Shorthand for 'svn diff -r N:M --old=URL --new=URL'.\n"
+ " 3. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] "
+ "--new=NEW-URL[@NEWREV]'\n"
        "\n"
- " 3. Shorthand for 'svn diff [-r N[:M]] --old=URL1 --new=URL2'\n"
- "\n"
        " Use just 'svn diff' to display local modifications in "
        "a working copy.\n"),
     {'r', svn_cl__old_cmd_opt, svn_cl__new_cmd_opt, 'x', 'N',

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat May 8 16:59:54 2004

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.