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; }