Author: pburba
Date: Thu Sep 2 18:10:01 2010
New Revision: 992042
URL: http://svn.apache.org/viewvc?rev=992042&view=rev
Log:
Fix issue #2915 'Handle mergeinfo for subtrees missing due to removal by
non-svn command'.
With this change, if you attempt a merge-tracking aware merge to a WC
which is missing subtrees due to an OS-level deletion, then an error is
raised before any editor drives begin. The error message describes the
root of each missing path.
* subversion/libsvn_client/merge.c
(get_mergeinfo_walk_baton): Add some new members for tracking sub-
directories' dirents.
(record_missing_subtree_roots): New function.
(get_mergeinfo_walk_cb): Use new function to flag missing subtree
roots.
(get_mergeinfo_paths): Raise a SVN_ERR_CLIENT_NOT_READY_TO_MERGE error if
any unexpectedly missing subtrees are found.
* subversion/tests/cmdline/merge_authz_tests.py
(skipped paths get overriding mergeinfo): Don't test the file missing via
OS-delete scenario, this is now covered in a much clearer manner in the
new merge_tests.py.merge_with_os_deleted_subtrees test. Also remove not
about testing a missing directory, that is also covered in the new test.
* subversion/tests/cmdline/merge_tests.py
(merge_into_missing): Use --ignore-ancestry during this test's merge to
disregard mergeinfo and preserve the original intent of the test.
(skipped_files_get_correct_mergeinfo): Use a shallow WC to rather than an
OS-level delete to test issue #3440. Remove the second part of this test
which has no relevance now that merge tracking doesn't tolerate subtrees
missing via OS-deletion.
(merge_with_os_deleted_subtrees): New test.
(test_list): Add merge_with_os_deleted_subtrees.
* subversion/tests/cmdline/merge_tree_conflict_tests.py
(tree_conflicts_merge_edit_onto_missing,
tree_conflicts_merge_del_onto_missing): Use --ignore-ancestry during these
tests' merges to disregard mergeinfo and preserve the original intent of
the test. Don't bother with the post-merge commit either, since there is
nothing to commit as there are no mergeinfo changes.
* subversion/tests/cmdline/svntest/actions.py
(deep_trees_run_tests_scheme_for_merge): Add new args allowing caller to
use --ignore-ancestry during the merge and to skip the post merge commit
if desired.
Modified:
subversion/trunk/subversion/libsvn_client/merge.c
subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py
subversion/trunk/subversion/tests/cmdline/merge_tests.py
subversion/trunk/subversion/tests/cmdline/merge_tree_conflict_tests.py
subversion/trunk/subversion/tests/cmdline/svntest/actions.py
Modified: subversion/trunk/subversion/libsvn_client/merge.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/merge.c?rev=992042&r1=992041&r2=992042&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/merge.c (original)
+++ subversion/trunk/subversion/libsvn_client/merge.c Thu Sep 2 18:10:01 2010
@@ -5304,6 +5304,17 @@ struct get_mergeinfo_walk_baton
{
/* Array of paths that have explicit mergeinfo and/or are switched. */
apr_array_header_t *children_with_mergeinfo;
+
+ /* A hash of MERGE_TARGET_ABSPATH's subdirectories' dirents. Maps
+ const char * absolute working copy paths to dirent hashes as obtained
+ by svn_io_get_dirents3(). Contents are allocated in CB_POOL. */
+ apr_hash_t *subtree_dirents;
+
+ /* A hash to keep track of any subtrees in the merge target which are
+ unexpectedly missing from disk. Maps const char * absolute working
+ copy paths to the same. Contents are allocated in CB_POOL. */
+ apr_hash_t *missing_subtrees;
+
/* Merge source canonical path. */
const char* merge_src_canon_path;
@@ -5324,8 +5335,111 @@ struct get_mergeinfo_walk_baton
/* Pool from which to allocate new elements of CHILDREN_WITH_MERGEINFO. */
apr_pool_t *pool;
+
+ /* Pool with a lifetime guaranteed over all the get_mergeinfo_walk_cb
+ callbacks. */
+ apr_pool_t *cb_pool;
};
+/* Helper for the svn_wc__node_found_func_t callback get_mergeinfo_walk_cb().
+
+ Checks for issue #2915 subtrees, i.e. those that the WC thinks are on disk
+ but have been removed due to an OS-level deletion.
+
+ If the supposed working path LOCAL_ABSPATH, of kind KIND, is the root
+ of a missing subtree, then add a (const char *) WC absolute path to
+ (const char *) WC absolute path mapping to MISSING_SUBTREES, where the
+ paths are both a copy of LOCAL_ABSPATH, allocated in RESULT_POOL.
+
+ If LOCAL_ABSPATH is a directory and is not missing from disk, then add
+ a (const char *) WC absolute path to (svn_io_dirent2_t *) dirent mapping
+ to SUBTREE_DIRENTS, again allocated in RESULT_POOL (see
+ svn_io_get_dirents3).
+
+ SCRATCH_POOL is used for temporary allocations.
+
+ Note: Since this is effetively a svn_wc__node_found_func_t callback, it
+ must be called in depth-first order. */
+static svn_error_t *
+record_missing_subtree_roots(const char *local_abspath,
+ svn_node_kind_t kind,
+ apr_hash_t *subtree_dirents,
+ apr_hash_t *missing_subtrees,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t missing_subtree_root = FALSE;
+
+ /* Store the dirents for each directory in SUBTREE_DIRENTS. */
+ if (kind == svn_node_dir)
+ {
+ /* If SUBTREE_DIRENTS is empty LOCAL_ABSPATH is merge target. */
+ if (apr_hash_count(subtree_dirents) == 0
+ || apr_hash_get(subtree_dirents,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ APR_HASH_KEY_STRING))
+ {
+ apr_hash_t *dirents;
+ svn_error_t *err = svn_io_get_dirents3(&dirents, local_abspath,
+ TRUE, result_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ /* We can't get this directory's dirents, it's missing
+ from disk. */
+ svn_error_clear(err);
+ missing_subtree_root = TRUE;
+ }
+ else
+ {
+ return err;
+ }
+ }
+ else
+ {
+ apr_hash_set(subtree_dirents,
+ apr_pstrdup(result_pool, local_abspath),
+ APR_HASH_KEY_STRING, dirents);
+ }
+ }
+ }
+ else /* kind != svn_node_dir */
+ {
+ /* Is this non-directory missing from disk? Check LOCAL_ABSPATH's
+ parent's dirents. */
+ apr_hash_t *parent_dirents = apr_hash_get(subtree_dirents,
+
svn_dirent_dirname(local_abspath,
+
scratch_pool),
+ APR_HASH_KEY_STRING);
+
+ /* If the parent_dirents is NULL, then LOCAL_ABSPATH is the
+ subtree of a missing subtree. Since we only report the roots
+ of missing subtrees there is nothing more to do in that case. */
+ if (parent_dirents)
+ {
+ svn_io_dirent2_t *dirent =
+ apr_hash_get(parent_dirents,
+ svn_dirent_basename(local_abspath, scratch_pool),
+ APR_HASH_KEY_STRING);
+ if (!dirent)
+ missing_subtree_root = TRUE;
+ }
+ }
+
+ if (missing_subtree_root)
+ {
+ const char *path = apr_pstrdup(result_pool, local_abspath);
+
+ apr_hash_set(missing_subtrees, path,
+ APR_HASH_KEY_STRING, path);
+ }
+
+ return SVN_NO_ERROR;
+}
/* svn_wc__node_found_func_t callback for get_mergeinfo_paths().
@@ -5410,6 +5524,17 @@ get_mergeinfo_walk_cb(const char *local_
&&(kind == svn_node_dir)
&& (strcmp(abs_parent_path,
wb->merge_target_abspath) == 0));
+ /* Make sure what the WC thinks is present on disk really is. */
+#ifndef SVN_WC__SINGLE_DB
+ if (!absent && !deleted && !obstructed)
+#else
+ if (!absent && !deleted)
+#endif
+ SVN_ERR(record_missing_subtree_roots(local_abspath, kind,
+ wb->subtree_dirents,
+ wb->missing_subtrees,
+ wb->cb_pool,
+ scratch_pool));
/* Store PATHs with explict mergeinfo, which are switched, are missing
children due to a sparse checkout, are scheduled for deletion are absent
@@ -5628,7 +5753,7 @@ insert_parent_and_sibs_of_sw_absent_del_
/* Helper for do_directory_merge()
If HONOR_MERGEINFO is TRUE, then perform a depth first walk of the working
- copy tree rooted at MERGE_CMD_BATON->TARGET_ABSPATH.
+ copy tree rooted at MERGE_CMD_BATON->TARGET_ABSPATH to depth DEPTH.
Create an svn_client__merge_path_t * for any path which meets one or more
of the following criteria:
@@ -5656,6 +5781,9 @@ insert_parent_and_sibs_of_sw_absent_del_
If HONOR_MERGEINFO is FALSE, then create an svn_client__merge_path_t * only
for MERGE_CMD_BATON->TARGET_ABSPATH (i.e. only criteria 7 is applied).
+ If subtrees within the requested DEPTH are unexpectedly missing disk,
+ then raise SVN_ERR_CLIENT_NOT_READY_TO_MERGE.
+
Store the svn_client__merge_path_t *'s in *CHILDREN_WITH_MERGEINFO in
depth-first order based on the svn_client__merge_path_t *s path member as
sorted by svn_path_compare_paths(). Set the remaining_ranges field of each
@@ -5695,6 +5823,9 @@ get_mergeinfo_paths(apr_array_header_t *
struct get_mergeinfo_walk_baton wb = { 0 };
wb.children_with_mergeinfo = children_with_mergeinfo;
+ wb.cb_pool = svn_pool_create(scratch_pool);
+ wb.subtree_dirents = apr_hash_make(wb.cb_pool);
+ wb.missing_subtrees = apr_hash_make(wb.cb_pool);
wb.merge_src_canon_path = merge_src_canon_path;
wb.merge_target_abspath = merge_cmd_baton->target_abspath;
wb.source_root_url = source_root_url;
@@ -5718,6 +5849,37 @@ get_mergeinfo_paths(apr_array_header_t *
merge_cmd_baton->ctx->cancel_baton,
scratch_pool));
+ if (apr_hash_count(wb.missing_subtrees))
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *missing_subtree_err_str = NULL;
+
+ for (hi = apr_hash_first(iterpool, wb.missing_subtrees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *missing_abspath = svn__apr_hash_index_key(hi);
+
+ missing_subtree_err_str = apr_psprintf(
+ scratch_pool, "%s%s\n",
+ missing_subtree_err_str ? missing_subtree_err_str : "",
+ svn_dirent_local_style(missing_abspath, scratch_pool));
+ }
+
+ if (missing_subtree_err_str)
+ return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE,
+ NULL,
+ _("Merge tracking not allowed with missing "
+ "subtrees; try restoring these items "
+ "first:\n%s"),
+ missing_subtree_err_str);
+ }
+
+ /* This pool is only needed across all the callbacks to detect
+ missing subtrees. */
+ svn_pool_destroy(wb.cb_pool);
+
/* CHILDREN_WITH_MERGEINFO must be in depth first order, but the node
walk code returns nodes in a non particular order. Also, we may need
to add elements to the array to cover case 3) through 5) from the
Modified: subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py?rev=992042&r1=992041&r2=992042&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py Thu Sep 2
18:10:01 2010
@@ -82,13 +82,6 @@ def mergeinfo_and_skipped_paths(sbox):
# 2) Destination of merge is inaccessible due to authz restrictions.
# 3) Source *and* destination of merge is inaccessible due to authz
# restrictions.
- # 4) File path is versioned but is missing from disk due to OS deletion.
- # This isn't technically part of issue #2893 but we handle this case
- # and it didn't warrant its own test).
- #
- # Eventually we should also test(?):
- #
- # 5) Dir path is versioned but is missing from disk due to an OS deletion.
sbox.build()
wc_dir = sbox.wc_dir
@@ -122,27 +115,20 @@ def mergeinfo_and_skipped_paths(sbox):
omega_path = os.path.join(wc_restricted, "A_COPY", "D", "H", "omega")
zeta_path = os.path.join(wc_dir, "A", "D", "H", "zeta")
- # Restrict access to some more of the merge destination the
- # old fashioned way, delete it via the OS.
- ### TODO: Delete a versioned directory?
- os.remove(omega_path)
-
# Merge r4:8 into the restricted WC's A_COPY.
#
# We expect A_COPY/B/E to be skipped because we can't access the source
# and A_COPY/D/H/omega because it is missing. Since we have A_COPY/B/E
# we should override it's inherited mergeinfo, giving it just what it
- # inherited from A_COPY before the merge. omega is missing, but since
- # it is a file we can record the fact that it is missing in its parent
- # directory A_COPY/D/H.
+ # inherited from A_COPY before the merge.
expected_output = wc.State(A_COPY_path, {
'D/G/rho' : Item(status='U '),
'D/H/psi' : Item(status='U '),
+ 'D/H/omega' : Item(status='U '),
})
expected_mergeinfo_output = wc.State(A_COPY_path, {
'' : Item(status=' U'),
'B/E' : Item(status=' U'),
- 'D/H/omega' : Item(status=' U'),
})
expected_elision_output = wc.State(A_COPY_path, {
})
@@ -150,7 +136,7 @@ def mergeinfo_and_skipped_paths(sbox):
'' : Item(status=' M', wc_rev=8),
'D/H/chi' : Item(status=' ', wc_rev=8),
'D/H/psi' : Item(status='M ', wc_rev=8),
- 'D/H/omega' : Item(status='!M', wc_rev=8),
+ 'D/H/omega' : Item(status='M ', wc_rev=8),
'D/H' : Item(status=' ', wc_rev=8),
'D/G/pi' : Item(status=' ', wc_rev=8),
'D/G/rho' : Item(status='M ', wc_rev=8),
@@ -171,9 +157,7 @@ def mergeinfo_and_skipped_paths(sbox):
'' : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}),
'D/H/psi' : Item("New content"),
'D/H/chi' : Item("This is the file 'chi'.\n"),
- # 'D/H/omega' : run_and_verify_merge() doesn't support checking
- # the props on a missing path, so we do that
- # manually (see below).
+ 'D/H/omega' : Item("New content"),
'D/H' : Item(),
'D/G/pi' : Item("This is the file 'pi'.\n"),
'D/G/rho' : Item("New content"),
@@ -192,7 +176,6 @@ def mergeinfo_and_skipped_paths(sbox):
})
expected_skip = wc.State(A_COPY_path, {
'B/E' : Item(),
- 'D/H/omega' : Item(),
})
svntest.actions.run_and_verify_merge(A_COPY_path, '4', '8',
sbox.repo_url + '/A', None,
@@ -205,10 +188,6 @@ def mergeinfo_and_skipped_paths(sbox):
None, None, None, None,
None, 1)
- # Manually check the props on A_COPY/D/H/omega.
- svntest.actions.run_and_verify_svn(None, ['\n'], [],
- 'pg', SVN_PROP_MERGEINFO, omega_path)
-
# Merge r4:8 into the restricted WC's A_COPY_2.
#
# As before we expect A_COPY_2/B/E to be skipped because we can't access the
Modified: subversion/trunk/subversion/tests/cmdline/merge_tests.py
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/merge_tests.py?rev=992042&r1=992041&r2=992042&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/merge_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/merge_tests.py Thu Sep 2
18:10:01 2010
@@ -1733,6 +1733,10 @@ def merge_into_missing(sbox):
'Q/baz' : Item(status='! ', wc_rev='3'),
})
+ # Use --ignore-ancestry because merge tracking aware merges raise an
+ # error when the merge target is missing subtrees due to OS-level
+ # deletes.
+
### Need to real and dry-run separately since real merge notifies Q
### twice!
svntest.actions.run_and_verify_merge(F_path, '1', '2', F_url, None,
@@ -1743,23 +1747,20 @@ def merge_into_missing(sbox):
expected_status,
expected_skip,
None, None, None, None, None,
- 0, 0, '--dry-run', F_path)
+ 0, 0, '--dry-run',
+ '--ignore-ancestry', F_path)
expected_status = wc.State(F_path, {
- '' : Item(status=' M', wc_rev=1),
- 'foo' : Item(status='!M', wc_rev=2),
+ '' : Item(status=' ', wc_rev=1),
+ 'foo' : Item(status='! ', wc_rev=2),
'Q' : Item(status='! ', wc_rev='?'),
})
expected_mergeinfo_output = wc.State(F_path, {
- '' : Item(status=' U'),
- 'foo' : Item(status=' U'), # Mergeinfo is set on missing/obstructed files.
})
if single_db:
# Revision is known and we can record mergeinfo
expected_status.tweak('Q', wc_rev='2', entry_rev='?')
- expected_mergeinfo_output.add({'Q' : Item(status=' U')})
- # Missing data still available
expected_status.add({
'Q/R' : Item(status='! ', wc_rev='3'),
'Q/R/bar' : Item(status='! ', wc_rev='3'),
@@ -1774,7 +1775,8 @@ def merge_into_missing(sbox):
expected_status,
expected_skip,
None, None, None, None, None,
- 0, 0)
+ 0, 0,
+ '--ignore-ancestry', F_path)
# This merge fails when it attempts to descend into the missing
# directory. That's OK, there is no real need to support merge into
@@ -1789,8 +1791,8 @@ def merge_into_missing(sbox):
# Check working copy is not locked.
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.add({
- 'A/B/F' : Item(status=' M', wc_rev=1),
- 'A/B/F/foo' : Item(status='!M', wc_rev=2),
+ 'A/B/F' : Item(status=' ', wc_rev=1),
+ 'A/B/F/foo' : Item(status='! ', wc_rev=2),
'A/B/F/Q' : Item(status='! ', wc_rev='?'),
})
if single_db:
@@ -14995,6 +14997,7 @@ def skipped_files_get_correct_mergeinfo(
# Some paths we'll care about
A_COPY_path = os.path.join(wc_dir, "A_COPY")
+ H_COPY_path = os.path.join(wc_dir, "A_COPY", "D", "H")
psi_COPY_path = os.path.join(wc_dir, "A_COPY", "D", "H", "psi")
psi_path = os.path.join(wc_dir, "A", "D", "H", "psi")
@@ -15022,15 +15025,21 @@ def skipped_files_get_correct_mergeinfo(
[], 'merge', '-c3', sbox.repo_url + '/A', A_COPY_path)
svntest.main.run_svn(None, 'commit', '-m', 'initial merge', wc_dir)
- # Update WC to uniform revision and then delete, via the OS, A_COPY/D/H/psi
- # and then merge all available revisions from A to A_COPY. A_COPY/D/H/psi
- # should be reported as skipped and get explicit mergeinfo set on it
- # reflecting what it previously inherited from A_COPY after the first
- # merge, i.e. '/A/D/H/psi:3'. Issue #3440 occurred when empty mergeinfo
- # was set on A_COPY/D/H/psi, making it appear that r3 was never merged.
+ # Update WC to uniform revision and then set the depth on A_COPY/D/H to
+ # empty. Then merge all available revisions from A to A_COPY.
+ # A_COPY/D/H/psi and A_COPY/D/H/omega are not present due to their
+ # parent's depth and should be reported as skipped. A_COPY/D/H should
+ # get explicit mergeinfo set on it reflecting what it previously inherited
+ # from A_COPY after the first merge, i.e. '/A/D/H:3', plus non-inheritable
+ # mergeinfo describing what was done during this merge,
+ # i.e. '/A/D/H:2*,4-8*'.
+ #
+ # Issue #3440 occurred when empty mergeinfo was set on A_COPY/D/H, making
+ # it appear that r3 was never merged.
svntest.actions.run_and_verify_svn(None, ["At revision 8.\n"], [],
'up', wc_dir)
- os.remove(psi_COPY_path)
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'up', '--set-depth=empty', H_COPY_path)
expected_status = wc.State(A_COPY_path, {
'' : Item(status=' M'),
'B' : Item(status=' '),
@@ -15047,10 +15056,7 @@ def skipped_files_get_correct_mergeinfo(
'D/G/rho' : Item(status='M '),
'D/G/tau' : Item(status=' '),
'D/gamma' : Item(status=' '),
- 'D/H' : Item(status=' '),
- 'D/H/chi' : Item(status=' '),
- 'D/H/psi' : Item(status='!M'),
- 'D/H/omega' : Item(status='M '),
+ 'D/H' : Item(status=' M'),
})
expected_status.tweak(wc_rev=8)
expected_disk = wc.State('', {
@@ -15069,71 +15075,18 @@ def skipped_files_get_correct_mergeinfo(
'D/G/rho' : Item("New content"),
'D/G/tau' : Item("This is the file 'tau'.\n"),
'D/gamma' : Item("This is the file 'gamma'.\n"),
- 'D/H' : Item(),
- 'D/H/chi' : Item("This is the file 'chi'.\n"),
- #'D/H/psi' : Nothing here, this file was deleted via the OS.
- 'D/H/omega' : Item("New content"),
+ 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:2*,3,4-8*'}),
})
expected_skip = wc.State(A_COPY_path,
- {'D/H/psi' : Item()})
+ {'D/H/psi' : Item(),
+ 'D/H/omega' : Item()})
expected_output = wc.State(A_COPY_path,
{'B/E/beta' : Item(status='U '),
- 'D/G/rho' : Item(status='U '),
- 'D/H/omega' : Item(status='U '),})
- expected_mergeinfo_output = wc.State(A_COPY_path, {
- '' : Item(status=' U'),
- 'D/H/psi' : Item(status=' U'),
- })
- expected_elision_output = wc.State(A_COPY_path, {
- })
- svntest.actions.run_and_verify_merge(A_COPY_path, None, None,
- sbox.repo_url + '/A', None,
- expected_output,
- expected_mergeinfo_output,
- expected_elision_output,
- expected_disk,
- expected_status,
- expected_skip,
- None, None, None, None, None,
- 1, 1)
- # run_and_verify_merge cannot check the properties on A_COPY/D/H/psi
- # since that file is not on disk, so we'll check the file's mergeinfo
- # directly with svn propget.
- svntest.actions.run_and_verify_svn(
- 'Incorrect override mergeinfo set on skipped path',
- ["/A/D/H/psi:3\n"], [], 'pg', 'svn:mergeinfo', psi_COPY_path)
-
- # Now test another aspect of issue #3440, that a skipped path with
- # explicit mergeinfo doesn't get it's mergeinfo updated.
- #
- # First revert all changes to the WC and then merge -r2:6 from A/D/H/psi
- # to A_COPY/D/H/psi, creating explicit mergeinfo of '/A/D/H/psi:3-6' on
- # the latter. Commit this merge as r9 and then update the WC.
- svntest.actions.run_and_verify_svn(None, None, [],
- 'revert', '-R', wc_dir)
- svntest.actions.run_and_verify_svn(
- None,
- expected_merge_output([[3,6]],
- [' U ' + psi_COPY_path + '\n',
- ' G ' + psi_COPY_path + '\n']),
- [], 'merge', '-r2:6', sbox.repo_url + '/A/D/H/psi', psi_COPY_path)
- svntest.main.run_svn(None, 'commit', '-m',
- 'subtree merge to create explicit mergeinfo',
- wc_dir)
- svntest.actions.run_and_verify_svn(None, ["At revision 9.\n"], [],
- 'up', wc_dir)
-
- # Remove A_COPY/D/H/psi again and then merge all available revisions
- # from A to A_COPY. The results should be mostly similar to the
- # previous merge we did above, execept that A_COPY/D/H/psi should not
- # have it's mergeinfo changed.
- os.remove(psi_COPY_path)
- expected_status.tweak(wc_rev=9)
- expected_status.tweak('D/H/psi', status='! ')
- expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A:2-9'})
+ 'D/G/rho' : Item(status='U ')})
expected_mergeinfo_output = wc.State(A_COPY_path, {
- '' : Item(status=' U'),
- 'D/H/psi' : Item(status=' U'),
+ '' : Item(status=' U'),
+ 'D/H' : Item(status=' G'), # ' G' because override mergeinfo gets set
+ # on this, the root of a 'missing' subtree.
})
expected_elision_output = wc.State(A_COPY_path, {
})
@@ -15148,16 +15101,6 @@ def skipped_files_get_correct_mergeinfo(
None, None, None, None, None,
1, 1)
- # run_and_verify_merge cannot check the properties on A_COPY/D/H/psi
- # since that file is not on disk, so we'll check the file's mergeinfo
- # directly with svn propget. Issue #3440 also occurred here, when an
- # the missing file's mergeinfo was updated, making it appear that r2
- # and r7-9 were also merged into A_COPY/D/H/psi, which is clearly not
- # the case since psi isn't present.
- svntest.actions.run_and_verify_svn(
- 'Mergeinfo on skipped path altered',
- ["/A/D/H/psi:3-6\n"], [], 'pg', 'svn:mergeinfo', psi_COPY_path)
-
#----------------------------------------------------------------------
# Test for issue #3115 'Case only renames resulting from merges don't
# work or break the WC on case-insensitive file systems'.
@@ -15899,6 +15842,87 @@ def merge_into_locally_added_directory(s
True, True, new_dir_path)
sbox.simple_commit()
+#----------------------------------------------------------------------
+# Test for issue #2915 'Handle mergeinfo for subtrees missing due to removal
+# by non-svn command'
+def merge_with_os_deleted_subtrees(sbox):
+ "merge tracking fails if target missing subtrees"
+
+ # r1: Create a greek tree.
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # r2 - r6: Copy A to A_COPY and then make some text changes under A.
+ set_up_branch(sbox)
+
+ # Some paths we'll care about
+ A_COPY_path = os.path.join(wc_dir, "A_COPY")
+ C_COPY_path = os.path.join(wc_dir, "A_COPY", "C")
+ psi_COPY_path = os.path.join(wc_dir, "A_COPY", "D", "H", "psi")
+ mu_COPY_path = os.path.join(wc_dir, "A_COPY", "mu")
+ G_COPY_path = os.path.join(wc_dir, "A_COPY", "D", "G")
+
+ # Remove several subtrees from disk.
+ svntest.main.safe_rmtree(C_COPY_path)
+ svntest.main.safe_rmtree(G_COPY_path)
+ os.remove(psi_COPY_path)
+ os.remove(mu_COPY_path)
+
+ # Be sure the regex paths are properly escaped on Windows, see the
+ # note about "The Backslash Plague" in expected_merge_output().
+ if sys.platform == 'win32':
+ re_sep = '\\\\'
+ else:
+ re_sep = os.pathsep
+
+ # Common part of the expected error message for all cases we will test.
+ err_re = "svn: Merge tracking not allowed with missing subtrees; " + \
+ "try restoring these items first:" + \
+ "|(\n)" + \
+ "|(.*apr_err.*\n)" # In case of debug build
+
+ # Case 1: Infinite depth merge into infinite depth WC target.
+ # Every missing subtree under the target should be reported as missing.
+ missing = "|(.*A_COPY" + re_sep + "mu\n)" + \
+ "|(.*A_COPY" + re_sep + "D" + re_sep + "G\n)" + \
+ "|(.*A_COPY" + re_sep + "C\n)" + \
+ "|(.*A_COPY" + re_sep + "D" + re_sep + "H" + re_sep + "psi\n)"
+ exit_code, out, err = svntest.actions.run_and_verify_svn(
+ "Missing subtrees should raise error", [], svntest.verify.AnyOutput,
+ 'merge', sbox.repo_url + '/A', A_COPY_path)
+ svntest.verify.verify_outputs("Merge failed but not in the way expected",
+ err, None, err_re + missing, None,
+ True) # Match *all* lines of stderr
+
+ # Case 2: Immediates depth merge into infinite depth WC target.
+ # Only the two immediate children of the merge target should be reported
+ # as missing.
+ missing = "|(.*A_COPY" + re_sep + "mu\n)" + \
+ "|(.*A_COPY" + re_sep + "C\n)"
+ exit_code, out, err = svntest.actions.run_and_verify_svn(
+ "Missing subtrees should raise error", [], svntest.verify.AnyOutput,
+ 'merge', sbox.repo_url + '/A', A_COPY_path, '--depth=immediates')
+ svntest.verify.verify_outputs("Merge failed but not in the way expected",
+ err, None, err_re + missing, None, True)
+
+ # Case 3: Files depth merge into infinite depth WC target.
+ # Only the single file child of the merge target should be reported
+ # as missing.
+ missing = "|(.*A_COPY" + re_sep + "mu\n)"
+ exit_code, out, err = svntest.actions.run_and_verify_svn(
+ "Missing subtrees should raise error", [], svntest.verify.AnyOutput,
+ 'merge', sbox.repo_url + '/A', A_COPY_path, '--depth=files')
+ svntest.verify.verify_outputs("Merge failed but not in the way expected",
+ err, None, err_re + missing, None, True)
+
+ # Case 4: Empty depth merge into infinite depth WC target.
+ # Only the...oh, wait, the target is present and that is as deep
+ # as the merge goes, so this merge should succeed!
+ svntest.actions.run_and_verify_svn(
+ "Depth empty merge should succeed as long at the target is present",
+ svntest.verify.AnyOutput, [], 'merge', sbox.repo_url + '/A',
+ A_COPY_path, '--depth=empty')
+
########################################################################
# Run the tests
@@ -16087,6 +16111,7 @@ test_list = [ None,
copy_causes_phantom_eol_conflict,
merge_into_locally_added_file,
merge_into_locally_added_directory,
+ merge_with_os_deleted_subtrees,
]
if __name__ == '__main__':
Modified: subversion/trunk/subversion/tests/cmdline/merge_tree_conflict_tests.py
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/merge_tree_conflict_tests.py?rev=992042&r1=992041&r2=992042&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/merge_tree_conflict_tests.py
(original)
+++ subversion/trunk/subversion/tests/cmdline/merge_tree_conflict_tests.py Thu
Sep 2 18:10:01 2010
@@ -1272,16 +1272,28 @@ def tree_conflicts_merge_edit_onto_missi
# local tree missing (via shell delete), incoming leaf edit
+ # Note: In 1.7 merge tracking aware merges raise an error if the
+ # merge target has subtrees missing due to a shell delete. To
+ # preserve the original intent of this test we'll run the merge
+ # with the --ignore-ancestry option, which neither considers nor
+ # records mergeinfo. With this option the merge should "succeed"
+ # while skipping the missing paths. Of course with no mergeinfo
+ # recorded and everything skipped, there is nothing to commit, so
+ # unlike most of the tree conflict tests we don't bother with the
+ # final commit step.
+
sbox.build()
expected_output = wc.State('', {
})
expected_disk = disk_after_tree_del
+ # Don't expect any mergeinfo property changes because we run
+ # the merge with the --ignore-ancestry option.
expected_status = svntest.wc.State('', {
- '' : Item(status=' M', wc_rev=3),
+ '' : Item(status=' ', wc_rev=3),
'F' : Item(status=' ', wc_rev=3),
- 'F/alpha' : Item(status='!M', wc_rev=3),
+ 'F/alpha' : Item(status='! ', wc_rev=3),
'D' : Item(status=' ', wc_rev=3),
'D/D1' : Item(status='! ', wc_rev='?'),
'DF' : Item(status=' ', wc_rev=3),
@@ -1327,18 +1339,7 @@ def tree_conflicts_merge_edit_onto_missi
expected_disk,
expected_status,
expected_skip,
-
- ### This should not be happening!
- ### The commit succeeds (it only commits mergeinfo).
- ### But then the work queue freaks out while trying to install
- ### F/alpha into the WC, because F/alpha is missing from disk.
- ### We end up with a working copy that cannot be cleaned up.
- ### To make this test pass for now we'll expect this error.
- ### When the problem is fixed this test will start to fail
- ### and should be adjusted.
- commit_block_string=".*Error bumping revisions post-commit",
-
- ) ], False)
+ ) ], False, do_commit_conflicts=False, ignore_ancestry=True)
#----------------------------------------------------------------------
def tree_conflicts_merge_del_onto_missing(sbox):
@@ -1346,16 +1347,28 @@ def tree_conflicts_merge_del_onto_missin
# local tree missing (via shell delete), incoming leaf edit
+ # Note: In 1.7 merge tracking aware merges raise an error if the
+ # merge target has subtrees missing due to a shell delete. To
+ # preserve the original intent of this test we'll run the merge
+ # with the --ignore-ancestry option, which neither considers nor
+ # records mergeinfo. With this option the merge should "succeed"
+ # while skipping the missing paths. Of course with no mergeinfo
+ # recorded and everything skipped, there is nothing to commit, so
+ # unlike most of the tree conflict tests we don't bother with the
+ # final commit step.
+
sbox.build()
expected_output = wc.State('', {
})
expected_disk = disk_after_tree_del
+ # Don't expect any mergeinfo property changes because we run
+ # the merge with the --ignore-ancestry option.
expected_status = svntest.wc.State('', {
- '' : Item(status=' M', wc_rev=3),
+ '' : Item(status=' ', wc_rev=3),
'F' : Item(status=' ', wc_rev=3),
- 'F/alpha' : Item(status='!M', wc_rev=3),
+ 'F/alpha' : Item(status='! ', wc_rev=3),
'D' : Item(status=' ', wc_rev=3),
'D/D1' : Item(status='! ', wc_rev='?'),
'DF' : Item(status=' ', wc_rev=3),
@@ -1401,18 +1414,7 @@ def tree_conflicts_merge_del_onto_missin
expected_disk,
expected_status,
expected_skip,
-
- ### This should not be happening!
- ### The commit succeeds (it only commits mergeinfo).
- ### But then the work queue freaks out while trying to install
- ### F/alpha into the WC, because F/alpha is missing from disk.
- ### We end up with a working copy that cannot be cleaned up.
- ### To make this test pass for now we'll expect this error.
- ### When the problem is fixed this test will start to fail
- ### and should be adjusted.
- commit_block_string=".*Error bumping revisions post-commit",
-
- ) ], False)
+ ) ], False, do_commit_conflicts=False, ignore_ancestry=True)
#----------------------------------------------------------------------
def merge_replace_setup(sbox):
Modified: subversion/trunk/subversion/tests/cmdline/svntest/actions.py
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svntest/actions.py?rev=992042&r1=992041&r2=992042&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/svntest/actions.py (original)
+++ subversion/trunk/subversion/tests/cmdline/svntest/actions.py Thu Sep 2
18:10:01 2010
@@ -2680,7 +2680,9 @@ def deep_trees_run_tests_scheme_for_swit
def deep_trees_run_tests_scheme_for_merge(sbox, greater_scheme,
- do_commit_local_changes):
+ do_commit_local_changes,
+ do_commit_conflicts=True,
+ ignore_ancestry=False):
"""
Runs a given list of tests for conflicts occuring at a merge operation.
@@ -2714,12 +2716,14 @@ def deep_trees_run_tests_scheme_for_merg
Then, in effect, the local changes are committed as well.
8) In each test case subdir, the "incoming" subdir is merged into the
- "local" subdir.
- This causes conflicts between the "local" state in the working
- copy and the "incoming" state from the incoming subdir.
-
- 9) A commit is performed in each separate container, to verify
- that each tree-conflict indeed blocks a commit.
+ "local" subdir. If ignore_ancestry is True, then the merge is done
+ with the --ignore-ancestry option, so mergeinfo is neither considered
+ nor recorded. This causes conflicts between the "local" state in the
+ working copy and the "incoming" state from the incoming subdir.
+
+ 9) If do_commit_conflicts is True, then a commit is performed in each
+ separate container, to verify that each tree-conflict indeed blocks
+ a commit.
The sbox parameter is just the sbox passed to a test function. No need
to call sbox.build(), since it is called (once) within this function.
@@ -2851,10 +2855,15 @@ def deep_trees_run_tests_scheme_for_merg
x_skip.copy()
x_skip.wc_dir = local
+ varargs = (local,)
+ if ignore_ancestry:
+ varargs = varargs + ('--ignore-ancestry',)
+
run_and_verify_merge(local, None, None, incoming, None,
x_out, None, None, x_disk, None, x_skip,
- error_re_string = test_case.error_re_string,
- dry_run = False)
+ test_case.error_re_string,
+ None, None, None, None,
+ False, False, *varargs)
run_and_verify_unquiet_status(local, x_status)
except:
print("ERROR IN: Tests scheme for merge: "
@@ -2864,21 +2873,22 @@ def deep_trees_run_tests_scheme_for_merg
# 9) Verify that commit fails.
- for test_case in greater_scheme:
- try:
- local = j(wc_dir, test_case.name, 'local')
-
- x_status = test_case.expected_status
- if x_status != None:
- x_status.copy()
- x_status.wc_dir = local
-
- run_and_verify_commit(local, None, x_status,
- test_case.commit_block_string,
- local)
- except:
- print("ERROR IN: Tests scheme for merge: "
- + "while checking commit-blocking in '%s'" % test_case.name)
- raise
+ if do_commit_conflicts:
+ for test_case in greater_scheme:
+ try:
+ local = j(wc_dir, test_case.name, 'local')
+
+ x_status = test_case.expected_status
+ if x_status != None:
+ x_status.copy()
+ x_status.wc_dir = local
+
+ run_and_verify_commit(local, None, x_status,
+ test_case.commit_block_string,
+ local)
+ except:
+ print("ERROR IN: Tests scheme for merge: "
+ + "while checking commit-blocking in '%s'" % test_case.name)
+ raise