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