Fix issue #2153: wc-to-url copies would wrongly include deleted items. Thanks to Ben Collins-Sussman for various helpful hints. Original patch by kfogel, tested and modified by me. * subversion/libsvn_client/commit_util.c (harvest_committables): If in copy_mode, request deleted entries too, and set the SVN_CLIENT_COMMIT_ITEM_DELETE flag on them. * subversion/tests/clients/cmdline/copy_tests.py (mixed_wc_to_url): New test. (test_list): Run it. Index: subversion/libsvn_client/commit_util.c =================================================================== --- subversion/libsvn_client/commit_util.c (revision 14705) +++ subversion/libsvn_client/commit_util.c (arbetskopia) @@ -187,8 +187,9 @@ non-NULL. JUST_LOCKED indicates whether to treat non-modified items with lock tokens as commit candidates. - If in COPY_MODE, the entry is treated as if it is destined to be - added with history as URL. + If in COPY_MODE, treat the entry as if it is destined to be added + with history as URL, and add 'deleted' entries to COMMITTABLES as + items to delete in the copy destination. If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to see if the user has cancelled the operation. */ @@ -280,7 +281,7 @@ recurse anyway, so... ) */ svn_error_t *err; const svn_wc_entry_t *e = NULL; - err = svn_wc_entries_read (&entries, adm_access, FALSE, pool); + err = svn_wc_entries_read (&entries, adm_access, copy_mode, pool); /* If we failed to get an entries hash for the directory, no sweat. Cleanup and move along. */ @@ -327,11 +328,20 @@ if ((entry->url) && (! copy_mode)) url = entry->url; - /* Check for the deletion case. Deletes can occur only when we are - not in "adds-only mode". They can be either explicit - (schedule == delete) or implicit (schedule == replace ::= delete+add). */ + /* Check for the deletion case. Deletes occur only when not in + "adds-only mode". We use the SVN_CLIENT_COMMIT_ITEM_DELETE flag + to represent two slightly different conditions: + + - The entry is marked as 'deleted'. When copying a mixed-rev wc, + we still need to send a delete for that entry, otherwise the + object will wrongly exist in the repository copy. + + - The entry is scheduled for deletion or replacement, which case + we need to send a delete either way. + */ if ((! adds_only) - && ((entry->schedule == svn_wc_schedule_delete) + && ((entry->deleted && entry->schedule == svn_wc_schedule_normal) + || (entry->schedule == svn_wc_schedule_delete) || (entry->schedule == svn_wc_schedule_replace))) { state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; @@ -358,6 +368,7 @@ /* Check for the copied-subtree addition case. */ if ((entry->copied || copy_mode) + && (! entry->deleted) && (entry->schedule == svn_wc_schedule_normal)) { svn_revnum_t p_rev = entry->revision - 1; /* arbitrary non-equal value */ Index: subversion/tests/clients/cmdline/copy_tests.py =================================================================== --- subversion/tests/clients/cmdline/copy_tests.py (revision 14705) +++ subversion/tests/clients/cmdline/copy_tests.py (arbetskopia) @@ -1558,6 +1558,65 @@ svntest.actions.run_and_verify_svn(None, svntest.SVNAnyOutput, None, 'copy', dir_path, dir_path) + +#---------------------------------------------------------------------- + +def mixed_wc_to_url(sbox): + "copy a complex mixed-rev wc" + + # For issue 2153. + # + # Copy a mixed-revision wc (that also has some uncommitted local + # mods, and an entry marked as 'deleted') to a URL. Make sure the + # copy gets the uncommitted mods, and does not contain the deleted + # file. + + sbox.build() + + wc_dir = sbox.wc_dir + url = svntest.main.current_repo_url + G_url = svntest.main.current_repo_url + '/A/D/G' + Z_url = svntest.main.current_repo_url + '/A/D/Z' + G_path = os.path.join(wc_dir, 'A', 'D', 'G') + pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') + rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + + # Remove A/D/G/pi, then commit that removal. + svntest.actions.run_and_verify_svn(None, None, [], 'rm', pi_path) + svntest.actions.run_and_verify_svn(None, None, [], + 'ci', '-m', "Delete pi.", wc_dir) + + # Make a modification to A/D/G/rho, then commit that modification. + svntest.main.file_append(rho_path, "\nFirst modification to rho.\n") + svntest.actions.run_and_verify_svn(None, None, [], + 'ci', '-m', "Modify rho.", wc_dir) + + # Make another modification to A/D/G/rho, but don't commit it. + svntest.main.file_append(rho_path, "Second modification to rho.\n") + + # Now copy local A/D/G to create new directory A/D/Z the repository. + svntest.actions.run_and_verify_svn(None, None, [], + 'cp', '-m', "Make a copy.", + G_path, Z_url) + + # Check out A/D/Z. If it has pi, that's a bug; or if its rho does + # not have the second local mod, that's also a bug. + svntest.main.safe_rmtree(wc_dir) + svntest.actions.run_and_verify_svn(None, None, [], + 'co', Z_url, wc_dir) + + if os.path.exists(os.path.join(wc_dir, 'pi')): + raise svntest.Failure + + fp = open(os.path.join(wc_dir, 'rho'), 'r') + found_it = 0 + for line in fp.readlines(): + if re.match("^Second modification to rho.", line): + found_it = 1 + if not found_it: + raise svntest.Failure + + ######################################################################## # Run the tests @@ -1592,6 +1651,7 @@ non_existent_url_to_url, old_dir_url_to_url, wc_copy_dir_to_itself, + mixed_wc_to_url, ] if __name__ == '__main__':