Hi!

[[[
Fix #3493 - svn patch skips missing dirs.

* subversion/libsvn_client/patch.c
  (svn_client_patch): Replace the last adm_access calls. Needed to be
    able to use the write-locks in svn_client__make_local_parents().
  (apply_textdiffs, apply_one_patch): Change ctx parameter to be non
    const.
  (apply_one_patch): Create intermediate missing dirs.
  (resolve_target_path): Don't mark the target to be skipped if there is
    no containing dir. Walk the tree to the wc-root checking if one of
    the dirs above our target is scheduled for deletion.

* subversion/tests/cmdline/patch_tests.py
  (patch_add_new_dir): New. Tests for adding files with missing dirs and
    the case where one of the dirs above is scheduled for deletion.

Patch by: Daniel Näslund <daniel{_AT_}longitudo.com>
]]]

Right now the code does not skip when one of the dirs in the patch
file is a wc file. But I'm not sure that it's needed.

Daniel
Index: subversion/tests/cmdline/patch_tests.py
===================================================================
--- subversion/tests/cmdline/patch_tests.py	(revision 897874)
+++ subversion/tests/cmdline/patch_tests.py	(arbetskopia)
@@ -833,7 +833,69 @@
                                        1, # dry-run
                                        '-p1')
 
+def patch_add_new_dir(sbox):
+  "apply a undiff patch with missing dirs"
+  
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  
+  patch_file_path = tempfile.mkstemp(dir=os.path.abspath(svntest.main.temp_dir))[1]
 
+  # The first diff is adding 'new' with two missing dirs. The second is 
+  # adding 'new' with one missing dir to a 'A' that is locally deleted. Should be
+  # skipped.
+  unidiff_patch = [
+    "Index: new\n",
+    "===================================================================\n",
+    "--- X/Y/new\t(revision 0)\n",
+    "+++ X/Y/new\t(revision 0)\n",
+    "@@ -0,0 +1 @@\n",
+    "+new\n",
+    "Index: new\n",
+    "===================================================================\n",
+    "--- A/C/Y/new\t(revision 0)\n",
+    "+++ A/C/Y/new\t(revision 0)\n",
+    "@@ -0,0 +1 @@\n",
+    "+new\n",
+  ]
+
+  C_path = os.path.join(wc_dir, 'A', 'C')
+  svntest.actions.run_and_verify_svn("Deleting C failed", None, [],
+                                     'rm', C_path)
+  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))
+
+  expected_output = [
+    'A    %s\n' % os.path.join(wc_dir, 'X', 'Y', 'new'),
+    'Skipped missing target: \'%s\'\n' % os.path.join('A', 'C', 'Y', 'new'),
+    'Summary of conflicts:\n',
+    '  Skipped paths: 1\n',
+  ]
+
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.add({'X/Y/new': Item(contents='new\n')})
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.add({'X' : Item(status='A ', wc_rev=0)})
+  expected_status.add({'X/Y' : Item(status='A ', wc_rev=0)})
+  expected_status.add({'X/Y/new' : Item(status='A ', wc_rev=0)})
+  expected_status.add({'A/C' : Item(status='D ', wc_rev=1)})
+
+  A_C_Y_new_path = os.path.join(wc_dir, 'A', 'C', 'Y', 'new')
+  expected_skip = wc.State('', {
+                           'A/C/Y/new' : Item() 
+                            })
+
+  svntest.actions.run_and_verify_patch(wc_dir, 
+                                       os.path.abspath(patch_file_path),
+                                       expected_output,
+                                       expected_disk,
+                                       expected_status,
+                                       expected_skip,
+                                       None, # expected err
+                                       1, # check-props
+                                       1) # dry-run
+
+
 ########################################################################
 #Run the tests
 
@@ -844,6 +906,7 @@
               patch_unidiff_offset,
               patch_chopped_leading_spaces,
               patch_unidiff_strip1,
+              patch_add_new_dir,
             ]
 
 if __name__ == '__main__':
Index: subversion/libsvn_client/patch.c
===================================================================
--- subversion/libsvn_client/patch.c	(revision 897874)
+++ subversion/libsvn_client/patch.c	(arbetskopia)
@@ -35,6 +35,7 @@
 #include "svn_pools.h"
 #include "svn_subst.h"
 #include "svn_wc.h"
+#include "client.h"
 
 #include "svn_private_config.h"
 #include "private/svn_diff_private.h"
@@ -261,54 +262,50 @@
   /* Find out if there is a suitable patch target at the target path,
    * and determine if the target should be skipped. */
   SVN_ERR(svn_io_check_path(target->abs_path, &target->kind, scratch_pool));
-  switch (target->kind)
-    {
-      case svn_node_file:
-        target->skipped = FALSE;
-        break;
-      case svn_node_none:
-        {
-          const char *dirname;
-          svn_node_kind_t kind;
 
-          /* The file is not there, that's fine. The patch might want to
-           * create it. But the containing directory of the target needs
-           * to exists. Otherwise we won't be able to apply the patch. */
-          dirname = svn_dirent_dirname(target->abs_path, scratch_pool);
-          SVN_ERR(svn_io_check_path(dirname, &kind, scratch_pool));
-          target->skipped = (kind != svn_node_dir);
-          break;
-        }
-      default:
-        target->skipped = TRUE;
-        break;
-    }
+  if (target->kind == svn_node_file || target->kind == svn_node_none)
+    target->skipped = FALSE;
+  else
+    target->skipped = TRUE;
 
   if (! target->skipped)
     {
       const svn_wc_entry_t *entry;
+      const char *abs_path = target->abs_path;
+      svn_boolean_t inside_wc = TRUE;
 
-      /* If the target is versioned, we should be able to get an entry. */
-      SVN_ERR(svn_wc__maybe_get_entry(&entry, wc_ctx,
-                                      target->abs_path,
-                                      svn_node_unknown,
-                                      TRUE, FALSE,
-                                      result_pool,
-                                      scratch_pool));
-      if (entry)
+      while (inside_wc)
         {
-          if (entry->schedule == svn_wc_schedule_delete)
+          /* If the target is versioned, we should be able to get an entry. */
+          SVN_ERR(svn_wc__maybe_get_entry(&entry, wc_ctx,
+                                          abs_path,
+                                          svn_node_unknown,
+                                          TRUE, FALSE,
+                                          result_pool,
+                                          scratch_pool));
+          if (entry)
             {
-              /* The target is versioned and scheduled for deletion,
-               * skip it. */
-              target->skipped = TRUE;
+              if (entry->schedule == svn_wc_schedule_delete)
+                {
+                  /* The target is versioned and scheduled for deletion,
+                   * skip it. */
+                  target->skipped = TRUE;
+                  break;
+                }
             }
+          else if (target->kind == svn_node_file)
+            {
+              /* The target is an unversioned file, skip it. */
+               target->skipped = TRUE;
+               break;
+            }
+
+          /* set path to parent */
+          abs_path = svn_dirent_dirname(abs_path, scratch_pool);
+
+          if (! svn_dirent_is_child(abs_wc_path, abs_path, scratch_pool))
+              inside_wc = FALSE;
         }
-      else if (target->kind == svn_node_file)
-        {
-          /* The target is an unversioned file, skip it. */
-           target->skipped = TRUE;
-        }
     }
 
   return SVN_NO_ERROR;
@@ -901,7 +898,7 @@
  * Do all allocations in POOL. */
 static svn_error_t *
 apply_one_patch(svn_patch_t *patch, const char *wc_path,
-                svn_boolean_t dry_run, const svn_client_ctx_t *ctx,
+                svn_boolean_t dry_run, svn_client_ctx_t *ctx,
                 int strip_count, apr_pool_t *pool)
 {
   patch_target_t *target;
@@ -1034,6 +1031,34 @@
 
               if (! dry_run)
                 {
+                  if (target->added)
+                    {
+                      svn_wc_notify_func2_t notify_func;
+                      const char *dir_abspath = 
+                                  svn_dirent_dirname(target->abs_path, pool);
+                      const char *wc_abspath;
+                        
+                       SVN_ERR(svn_dirent_get_absolute(&wc_abspath, 
+                                                       wc_path, pool));
+
+                      /* In case we've added several dirs we need to create
+                       * them before we can copy our patched file. */
+                      if (svn_dirent_is_child(wc_abspath, dir_abspath, pool))
+                        {
+                          /* Hacky. We don't want any notifications when adding
+                           * the dirs. Just one at the end when we add the file.
+                           * This way, we will get the same notifications as
+                           * when running with --dry-run. */
+                          notify_func = ctx->notify_func2;
+                          ctx->notify_func2 = NULL;
+
+                          SVN_ERR(svn_client__make_local_parents(dir_abspath,
+                                                                 TRUE, ctx, 
+                                                                 pool));
+                          ctx->notify_func2 = notify_func;
+                        }
+                    }
+
                   /* Copy the patched file on top of the target file. */
                   SVN_ERR(svn_io_copy_file(target->patched_path,
                                            target->abs_path, FALSE, pool));
@@ -1082,7 +1107,7 @@
  * Do all allocations in POOL.  */
 static svn_error_t *
 apply_textdiffs(const char *patch_path, const char *wc_path,
-                svn_boolean_t dry_run, const svn_client_ctx_t *ctx,
+                svn_boolean_t dry_run, svn_client_ctx_t *ctx,
                 int strip_count, apr_pool_t *pool)
 {
   svn_patch_t *patch;
@@ -1135,27 +1160,20 @@
                  svn_client_ctx_t *ctx,
                  apr_pool_t *pool)
 {
-  svn_wc_adm_access_t *adm_access;
   const char *abs_target;
 
   if (strip_count < 0)
     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
                             _("strip count must be positive"));
 
-  /* svn_wc_add4() and svn_wc_delete4() internally obtain an adm_access
-   * write lock, so lock the entire working copy, the old way, for now.
-   * <Bert> You need some kind of write lock to make sure another
-   *        concurrent client can't also update the WC via its entries cache
-   *        at the same time.. And currently access batons are the only write
-   *        locks we have */
   SVN_ERR(svn_dirent_get_absolute(&abs_target, target, pool));
-  SVN_ERR(svn_wc__adm_open_in_context(&adm_access, ctx->wc_ctx, abs_target,
-                                      TRUE, -1, ctx->cancel_func,
-                                      ctx->cancel_baton, pool));
 
+  SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, abs_target, pool, 
+                                     pool));
+
   SVN_ERR(apply_textdiffs(patch_path, target, dry_run, ctx, strip_count, pool));
 
-  SVN_ERR(svn_wc_adm_close2(adm_access, pool));
+  SVN_ERR(svn_wc__release_write_lock(ctx->wc_ctx, abs_target, pool));
 
   return SVN_NO_ERROR;
 }

Reply via email to