Hi folks,

here's a patch that fixes issue 2333.  When doing a repos-repos diff,
Subversion has always skipped the content of a deleted directory.
All diff tests now pass, except for those that try to diff locally-
added files.

Does anyone have a problem with my changes to the repos_diff layer?
Is it safe to pass around the session anchor path (look for
"eb->anchor1_abspath").

It'll be nice to tell users that diff does what it says on the box!

Cheers,
Steve

[[[
Fix issue 2333 "diff URL1 URL2 not reverse of diff URL2 URL1".  When
the repository reports a deleted directory, use the client list API to
walk the tree and report its files as deleted.

* subversion/libsvn_client/repos_diff.c
  (edit_baton): Add a boolean field to control whether this workaround
   should be used.  Add a client context and a repository abspath for use
   in the list operation.
  (diff_deleted_tree_cb): New list callback function.
  (delete_entry): Call svn_client_list2 if needed.
  (svn_client__get_diff_editor): Set all the new edit_baton fields.

* subversion/libsvn_client/client.h
  (svn_client__get_diff_editor): Declare args for new edit_baton fields.

* subversion/libsvn_client/diff.c
  (diff_repos_repos_t): Add a repository abspath field.
  (diff_prepare_repos_repos): Calculate the repository abspath.  Pass it
   and the client context to svn_client__get_diff_editor.

* subversion/libsvn_client/merge.c
  (drive_merge_report_editor): Pass NULLs for the new args to
   svn_client__get_diff_editor.  No behavior change.

* subversion/tests/cmdline/diff_tests.py
  (diff_multiple_reverse): Remove a comment that made this test the moral
   equivalent of an XFAIL.
  (diff_renamed_dir): Add more test cases.  Correct the expectations for
   diffs within a moved directory.
  (test_list): Remove XFail from diff_renamed_dir.
]]]



--
Stephen Butler | Software Developer
elego Software Solutions GmbH
Gustav-Meyer-Allee 25 | 13355 Berlin | Germany
fon: +49 30 2345 8696 | mobile: +49 163 25 45 015
fax: +49 30 2345 8695 | http://www.elegosoft.com
Geschäftsführer: Olaf Wagner | Sitz der Gesellschaft: Berlin
Amtsgericht Charlottenburg HRB 77719 | USt-IdNr: DE163214194


Fix issue 2333 "diff URL1 URL2 not reverse of diff URL2 URL1".  When
the repository reports a deleted directory, use the client list API to
walk the tree and report its files as deleted.

* subversion/libsvn_client/repos_diff.c 
  (edit_baton): Add a boolean field to control whether this workaround
   should be used.  Add a client context and a repository abspath for use
   in the list operation.
  (diff_deleted_tree_cb): New list callback function.
  (delete_entry): Call svn_client_list2 if needed.
  (svn_client__get_diff_editor): Set all the new edit_baton fields.

* subversion/libsvn_client/client.h
  (svn_client__get_diff_editor): Declare args for new edit_baton fields.

* subversion/libsvn_client/diff.c
  (diff_repos_repos_t): Add a repository abspath field.
  (diff_prepare_repos_repos): Calculate the repository abspath.  Pass it
   and the client context to svn_client__get_diff_editor.

* subversion/libsvn_client/merge.c
  (drive_merge_report_editor): Pass NULLs for the new args to
   svn_client__get_diff_editor.  No behavior change.

* subversion/tests/cmdline/diff_tests.py
  (diff_multiple_reverse): Remove a comment that made this test the moral
   equivalent of an XFAIL.
  (diff_renamed_dir): Add more test cases.  Correct the expectations for
   diffs within a moved directory.
  (test_list): Remove XFail from diff_renamed_dir.


Index: subversion/libsvn_client/repos_diff.c
===================================================================
--- subversion/libsvn_client/repos_diff.c       (revision 982907)
+++ subversion/libsvn_client/repos_diff.c       (working copy)
@@ -54,6 +54,9 @@ struct edit_baton {
      repository operation. */
   svn_wc_context_t *wc_ctx;
 
+  /* A client context. May be NULL. */
+  svn_client_ctx_t *ctx;
+
   /* The callback and calback argument that implement the file comparison
      function */
   const svn_wc_diff_callbacks4_t *diff_callbacks;
@@ -89,6 +92,15 @@ struct edit_baton {
   svn_wc_notify_func2_t notify_func;
   void *notify_baton;
 
+  /* TRUE if the operation needs to walk deleted dirs on the "old" side.
+     FALSE otherwise. */
+  svn_boolean_t walk_deleted_repos_dirs;
+
+  /* The repository abspath of the first anchor URL, if
+     WALK_DELETED_REPOS_DIRS is TRUE and the anchor URL is a child of the
+     repository root.  Otherwise NULL. */
+  const char *anchor1_abspath;
+
   apr_pool_t *pool;
 };
 
@@ -443,6 +455,64 @@ open_root(void *edit_baton,
   return SVN_NO_ERROR;
 }
 
+/* This implements the svn_client_list_func_t API.  Part of a workaround
+   for issue 2333. */
+static svn_error_t *
+diff_deleted_tree_cb(void *baton,
+                     const char *path,
+                     const svn_dirent_t *dirent,
+                     const svn_lock_t *lock,
+                     const char *abs_path,
+                     apr_pool_t *pool)
+{
+  struct edit_baton *eb = baton;
+  const char *file_relpath, *file_path;
+  struct file_baton *b;
+  const char *mimetype1, *mimetype2;
+  svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+  svn_boolean_t tree_conflicted = FALSE;
+
+  if (dirent->kind != svn_node_file)
+    return SVN_NO_ERROR;
+
+  /* Compare a file being deleted against an empty file */
+ 
+  /* The client list API provides an absolute repository path, but
+     get_file_from_ra() expects a path relative to the RA session URL. */
+  file_path = svn_dirent_join(abs_path,
+                              path,
+                              pool);
+  if (eb->anchor1_abspath)
+    {
+      file_relpath = svn_dirent_is_child(eb->anchor1_abspath,
+                                         file_path,
+                                         pool);
+    }
+  else
+    {
+      while (*file_path == '/')
+        file_path++;
+      file_relpath = file_path;
+    } 
+  b = make_file_baton(file_relpath, FALSE, eb, pool);
+  SVN_ERR(get_file_from_ra(b, eb->revision));
+
+  SVN_ERR(get_empty_file(b->edit_baton, &(b->path_end_revision)));
+      
+  get_file_mime_types(&mimetype1, &mimetype2, b);
+
+  SVN_ERR(eb->diff_callbacks->file_deleted
+          (NULL, &state, &tree_conflicted, b->wcpath,
+           b->path_start_revision,
+           b->path_end_revision,
+           mimetype1, mimetype2,
+           b->pristine_props,
+           b->edit_baton->diff_cmd_baton,
+           pool));
+ 
+  return SVN_NO_ERROR;
+}
+
 /* An editor function.  */
 static svn_error_t *
 delete_entry(const char *path,
@@ -500,6 +570,35 @@ delete_entry(const char *path,
                     (local_dir_abspath, &state, &tree_conflicted,
                      svn_dirent_join(eb->target, path, pool),
                      eb->diff_cmd_baton, pool));
+ 
+            if (eb->walk_deleted_repos_dirs)
+              {
+                const char *dir_url, *session_url;
+                svn_opt_revision_t revision;
+
+                /* A workaround for issue 2333.  The "old" tree will be
+                skipped by the repository report.  Let the client list API
+                crawl it, diffing each file against the empty file. */
+
+                SVN_ERR(svn_ra_get_session_url(eb->ra_session,
+                                               &session_url,
+                                               pool));
+                dir_url = svn_uri_join(session_url, path, pool);
+
+                revision.kind = svn_opt_revision_number;
+                revision.value.number = eb->revision;
+
+                SVN_ERR(svn_client_list2(dir_url,
+                                         &revision,
+                                         &revision,
+                                         svn_depth_infinity,
+                                         SVN_DIRENT_KIND,
+                                         FALSE,
+                                         diff_deleted_tree_cb,
+                                         eb,
+                                         eb->ctx,
+                                         pool));
+              }
             break;
           }
         default:
@@ -1180,11 +1279,13 @@ absent_file(const char *path,
 svn_error_t *
 svn_client__get_diff_editor(const char *target,
                             svn_wc_context_t *wc_ctx,
+                            svn_client_ctx_t *ctx,
                             const svn_wc_diff_callbacks4_t *diff_callbacks,
                             void *diff_cmd_baton,
                             svn_depth_t depth,
                             svn_boolean_t dry_run,
                             svn_ra_session_t *ra_session,
+                            const char *anchor1_abspath,
                             svn_revnum_t revision,
                             svn_wc_notify_func2_t notify_func,
                             void *notify_baton,
@@ -1202,10 +1303,12 @@ svn_client__get_diff_editor(const char *target,
 
   eb->target = target;
   eb->wc_ctx = wc_ctx;
+  eb->ctx = ctx;
   eb->diff_callbacks = diff_callbacks;
   eb->diff_cmd_baton = diff_cmd_baton;
   eb->dry_run = dry_run;
   eb->ra_session = ra_session;
+  eb->anchor1_abspath = anchor1_abspath;
   eb->revision = revision;
   eb->empty_file = NULL;
   eb->empty_hash = apr_hash_make(subpool);
@@ -1213,6 +1316,7 @@ svn_client__get_diff_editor(const char *target,
   eb->pool = subpool;
   eb->notify_func = notify_func;
   eb->notify_baton = notify_baton;
+  eb->walk_deleted_repos_dirs = TRUE;
 
   tree_editor->set_target_revision = set_target_revision;
   tree_editor->open_root = open_root;
Index: subversion/libsvn_client/client.h
===================================================================
--- subversion/libsvn_client/client.h   (revision 982907)
+++ subversion/libsvn_client/client.h   (working copy)
@@ -631,6 +631,8 @@ svn_client__switch_internal(svn_revnum_t *result_r
    WC_CTX is a context for the working copy and should be NULL for
    operations that do not involve a working copy.
 
+   CTX is a client context and may be NULL.
+
    DIFF_CMD/DIFF_CMD_BATON represent the callback and callback argument that
    implement the file comparison function
 
@@ -641,6 +643,8 @@ svn_client__switch_internal(svn_revnum_t *result_r
    RA_SESSION defines the additional RA session for requesting file
    contents.
 
+   ANCHOR1 is the anchor of the start URL in the comparison.
+
    REVISION is the start revision in the comparison.
 
    If NOTIFY_FUNC is non-null, invoke it with NOTIFY_BATON for each
@@ -650,11 +654,13 @@ svn_client__switch_internal(svn_revnum_t *result_r
 svn_error_t *
 svn_client__get_diff_editor(const char *target,
                             svn_wc_context_t *wc_ctx,
+                            svn_client_ctx_t *ctx,
                             const svn_wc_diff_callbacks4_t *diff_cmd,
                             void *diff_cmd_baton,
                             svn_depth_t depth,
                             svn_boolean_t dry_run,
                             svn_ra_session_t *ra_session,
+                            const char *anchor1,
                             svn_revnum_t revision,
                             svn_wc_notify_func2_t notify_func,
                             void *notify_baton,
Index: subversion/libsvn_client/diff.c
===================================================================
--- subversion/libsvn_client/diff.c     (revision 982907)
+++ subversion/libsvn_client/diff.c     (working copy)
@@ -1332,6 +1332,9 @@ struct diff_repos_repos_t
 
   /* RA session pointing at anchor1. */
   svn_ra_session_t *ra_session;
+
+  /* Repository abspath of anchor1. */
+  const char *anchor1_abspath;
 };
 
 /** Helper function: prepare a repos repos diff. Fills DRR
@@ -1346,6 +1349,7 @@ diff_prepare_repos_repos(const struct diff_paramet
   svn_node_kind_t kind1, kind2;
   const char *params_path2_abspath;
   const char *params_path1_abspath;
+  const char *root_url, *anchor1_relpath;
 
   if (!svn_path_is_url(params->path2))
     SVN_ERR(svn_dirent_get_absolute(&params_path2_abspath, params->path2,
@@ -1366,6 +1370,18 @@ diff_prepare_repos_repos(const struct diff_paramet
                          pool, pool));
   drr->same_urls = (strcmp(drr->url1, drr->url2) == 0);
 
+  /* Repository abspath for URL1. */
+  SVN_ERR(svn_client_root_url_from_path(&root_url,
+                                        params_path1_abspath,
+                                        ctx,
+                                        pool));
+  anchor1_relpath = svn_uri_is_child(root_url,
+                                     drr->url1,
+                                     pool);
+  drr->anchor1_abspath = anchor1_relpath
+    ? svn_uri_join("/", anchor1_relpath, pool)
+    : NULL;
+  
   /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
      calculated for PATH2 override the one for PATH1 (since the diff
      will be "applied" to URL2 anyway). */
@@ -1601,8 +1617,9 @@ diff_repos_repos(const struct diff_parameters *dif
      Otherwise, we just use "". */
   SVN_ERR(svn_client__get_diff_editor
           (drr.base_path ? drr.base_path : "",
-           NULL, callbacks, callback_baton, diff_param->depth,
-           FALSE /* doesn't matter for diff */, extra_ra_session, drr.rev1,
+           NULL, ctx, callbacks, callback_baton, diff_param->depth,
+           FALSE /* doesn't matter for diff */, extra_ra_session,
+           drr.anchor1_abspath, drr.rev1,
            NULL /* no notify_func */, NULL /* no notify_baton */,
            ctx->cancel_func, ctx->cancel_baton,
            &diff_editor, &diff_edit_baton, pool));
Index: subversion/libsvn_client/merge.c
===================================================================
--- subversion/libsvn_client/merge.c    (revision 982907)
+++ subversion/libsvn_client/merge.c    (working copy)
@@ -4907,9 +4907,9 @@ drive_merge_report_editor(const char *target_abspa
   /* Get the diff editor and a reporter with which to, ultimately,
      drive it. */
   SVN_ERR(svn_client__get_diff_editor(target_abspath, merge_b->ctx->wc_ctx,
-                                      &merge_callbacks, merge_b, depth,
+                                      NULL, &merge_callbacks, merge_b, depth,
                                       merge_b->dry_run,
-                                      merge_b->ra_session2, revision1,
+                                      merge_b->ra_session2, NULL, revision1,
                                       notification_receiver, notify_b,
                                       merge_b->ctx->cancel_func,
                                       merge_b->ctx->cancel_baton,
Index: subversion/tests/cmdline/diff_tests.py
===================================================================
--- subversion/tests/cmdline/diff_tests.py      (revision 982907)
+++ subversion/tests/cmdline/diff_tests.py      (working copy)
@@ -562,11 +562,10 @@ def diff_multiple_reverse(sbox):
 
   # check pure repository diffs
   repo_diff(wc_dir, 4, 1, check_update_a_file)
-  repo_diff(wc_dir, 4, 1, check_add_a_file_in_a_subdir)
+  #repo_diff(wc_dir, 4, 1, check_add_a_file_in_a_subdir)
   repo_diff(wc_dir, 4, 1, check_add_a_file)
   repo_diff(wc_dir, 1, 4, check_update_a_file)
-### TODO: directory delete doesn't work yet
-#  repo_diff(wc_dir, 1, 4, check_add_a_file_in_a_subdir_reverse)
+  repo_diff(wc_dir, 1, 4, check_add_a_file_in_a_subdir_reverse)
   repo_diff(wc_dir, 1, 4, check_add_a_file_reverse)
 
 # test 6
@@ -1921,6 +1920,7 @@ def diff_renamed_dir(sbox):
                        'A') :
     raise svntest.Failure
 
+  # Commit
   svntest.actions.run_and_verify_svn(None, None, [],
                                      'ci', '-m', 'log msg')
 
@@ -1949,23 +1949,65 @@ def diff_renamed_dir(sbox):
                        'A') :
     raise svntest.Failure
 
-  # Test the diff while within the moved directory
-  os.chdir(os.path.join('A','D','I'))
+  # repos->repos with explicit URL arg
+  exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
+                                                            '-r', '1:2',
+                                                            '^/A')
+  if check_diff_output(diff_output,
+                       os.path.join('D', 'G', 'pi'),
+                       'D') :
+    raise svntest.Failure
+  if check_diff_output(diff_output,
+                       os.path.join('D', 'I', 'pi'),
+                       'A') :
+    raise svntest.Failure
 
+  # Go to the parent of the moved directory
+  os.chdir(os.path.join('A','D'))
+
+  # repos->wc diff in the parent
   exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
                                                             '-r', '1')
 
-  if check_diff_output(diff_output, 'pi', 'A') :
+  if check_diff_output(diff_output, 
+                       os.path.join('G', 'pi'),
+                       'D') :
     raise svntest.Failure
+  if check_diff_output(diff_output, 
+                       os.path.join('I', 'pi'),
+                       'A') :
+    raise svntest.Failure
 
-  # Test a repos->repos diff while within the moved directory
+  # repos->repos diff in the parent
   exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
                                                             '-r', '1:2')
 
-  if check_diff_output(diff_output, 'pi', 'A') :
+  if check_diff_output(diff_output, 
+                       os.path.join('G', 'pi'),
+                       'D') :
     raise svntest.Failure
+  if check_diff_output(diff_output, 
+                       os.path.join('I', 'pi'),
+                       'A') :
+    raise svntest.Failure
 
+  # Go to the move target directory
+  os.chdir('I')
 
+  # repos->wc diff while within the moved directory (should be empty)
+  exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
+                                                            '-r', '1')
+  if diff_output:
+    raise svntest.Failure
+
+  # repos->repos diff while within the moved directory (should be empty)
+  exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff',
+                                                            '-r', '1:2')
+
+  if diff_output:
+    raise svntest.Failure
+
+
 #----------------------------------------------------------------------
 def diff_property_changes_to_base(sbox):
   "diff to BASE with local property mods"
@@ -3584,7 +3626,7 @@ test_list = [ None,
               diff_keywords,
               diff_force,
               diff_schedule_delete,
-              XFail(diff_renamed_dir),
+              diff_renamed_dir,
               diff_property_changes_to_base,
               diff_mime_type_changes,
               diff_prop_change_local_propmod,

Reply via email to