Hi,
It's been a year since my last commit and I've forgotten about the
internals of the patch code. Sending this patch for review so that I
don't mess something up.
[[[
Fix issue #4049 - SEGV on patch that deletes and skips.
When we do the sanity check of the supplied patch file we set
skipped=TRUE for the files that we're not going to process.
* subversion/libsvn_client/patch.c
(patch_target_info_t): Add SKIPPED field.
(delete_empty_dirs): Don't process targets that are marked skipped.
* subversion/tests/cmdline/patch_tests.py
(patch_delete_and_skip): New.
(test_list): Add new test.
]]]
I could have checked if local_abspath was NULL but I figured it might be
easy to forget to set local_abspath = NULL in one of the many places
were we set the skipped flag.
--
Daniel (dannas)
Index: subversion/tests/cmdline/patch_tests.py
===================================================================
--- subversion/tests/cmdline/patch_tests.py (revision 1199347)
+++ subversion/tests/cmdline/patch_tests.py (arbetskopia)
@@ -3913,6 +3913,84 @@ def patch_dev_null(sbox):
1, # check-props
1) # dry-run
+@Issue(4049)
+def patch_delete_and_skip(sbox):
+ "patch that deletes and skips"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ patch_file_path = make_patch_path(sbox)
+
+ os.chdir(wc_dir)
+
+ # We need to use abspaths to trigger the segmentation fault.
+ abs = os.path.abspath('.')
+ if sys.platform == 'win32':
+ abs = abs.replace("\\", "/")
+
+ outside_wc = os.path.join(os.pardir, 'X')
+ if sys.platform == 'win32':
+ outside_wc = outside_wc.replace("\\", "/")
+
+ unidiff_patch = [
+ "Index: %s/A/B/E/alpha\n" % abs,
+ "===================================================================\n",
+ "--- %s/A/B/E/alpha\t(revision 1)\n" % abs,
+ "+++ %s/A/B/E/alpha\t(working copy)\n" % abs,
+ "@@ -1 +0,0 @@\n",
+ "-This is the file 'alpha'.\n",
+ "Index: %s/A/B/E/beta\n" % abs,
+ "===================================================================\n",
+ "--- %s/A/B/E/beta\t(revision 1)\n" % abs,
+ "+++ %s/A/B/E/beta\t(working copy)\n" % abs,
+ "@@ -1 +0,0 @@\n",
+ "-This is the file 'beta'.\n",
+ "Index: %s/A/B/E/out-of-reach\n" % abs,
+ "===================================================================\n",
+ "--- %s/iota\t(revision 1)\n" % outside_wc,
+ "+++ %s/iota\t(working copy)\n" % outside_wc,
+ "\n",
+ "Property changes on: iota\n",
+ "___________________________________________________________________\n",
+ "Added: propname\n",
+ "## -0,0 +1 ##\n",
+ "+propvalue\n",
+ ]
+
+ svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))
+
+ skipped_path = os.path.join(os.pardir, 'X', 'iota')
+ expected_output = [
+ 'D %s\n' % os.path.join('A', 'B', 'E', 'alpha'),
+ 'D %s\n' % os.path.join('A', 'B', 'E', 'beta'),
+ 'Skipped missing target: \'%s\'\n' % skipped_path,
+ 'D %s\n' % os.path.join('A', 'B', 'E'),
+ 'Summary of conflicts:\n',
+ ' Skipped paths: 1\n'
+ ]
+
+ expected_disk = svntest.main.greek_state.copy()
+ expected_disk.remove('A/B/E/alpha')
+ expected_disk.remove('A/B/E/beta')
+ expected_disk.remove('A/B/E')
+
+ expected_status = svntest.actions.get_virginal_state('.', 1)
+ expected_status.tweak('A/B/E', status='D ')
+ expected_status.tweak('A/B/E/alpha', status='D ')
+ expected_status.tweak('A/B/E/beta', status='D ')
+
+ expected_skip = wc.State('', {skipped_path: Item()})
+
+ svntest.actions.run_and_verify_patch('.', 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
@@ -3954,6 +4032,7 @@ test_list = [ None,
patch_reversed_add_with_props,
patch_reversed_add_with_props2,
patch_dev_null,
+ patch_delete_and_skip,
]
if __name__ == '__main__':
Index: subversion/libsvn_client/patch.c
===================================================================
--- subversion/libsvn_client/patch.c (revision 1199347)
+++ subversion/libsvn_client/patch.c (arbetskopia)
@@ -255,6 +255,7 @@ typedef struct patch_target_t {
typedef struct patch_target_info_t {
const char *local_abspath;
svn_boolean_t deleted;
+ svn_boolean_t skipped;
} patch_target_info_t;
@@ -2730,6 +2731,11 @@ delete_empty_dirs(apr_array_header_t *targets_info
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
target_info = APR_ARRAY_IDX(targets_info, i, patch_target_info_t *);
+
+ /* Skipped dirs will not have a local_abspath set. */
+ if (target_info->skipped)
+ continue;
+
parent = svn_dirent_dirname(target_info->local_abspath, iterpool);
if (apr_hash_get(non_empty_dirs, parent, APR_HASH_KEY_STRING))
@@ -2915,6 +2921,7 @@ apply_patches(void *baton,
target_info->local_abspath = apr_pstrdup(scratch_pool,
target->local_abspath);
target_info->deleted = target->deleted;
+ target_info->skipped = target->skipped;
APR_ARRAY_PUSH(targets_info,
patch_target_info_t *) = target_info;