Index: subversion/libsvn_wc/copy.c =================================================================== --- subversion/libsvn_wc/copy.c (revision 19715) +++ subversion/libsvn_wc/copy.c (working copy) @@ -99,9 +99,9 @@ (SVN_ERR_UNVERSIONED_RESOURCE, NULL, _("Cannot copy or move '%s': it's not under version control"), svn_path_local_style(src_path, pool)); - if ((src_entry->schedule == svn_wc_schedule_add) - || (! src_entry->url) - || (src_entry->copied)) + if (((src_entry->schedule == svn_wc_schedule_add) && + (!src_entry->copied)) + || (! src_entry->url)) return svn_error_createf (SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot copy or move '%s': it's not in the repository yet; " @@ -116,8 +116,14 @@ svn_revnum_t copyfrom_rev; apr_hash_t *props, *base_props; - SVN_ERR(svn_wc_get_ancestry(©from_url, ©from_rev, - src_path, src_access, pool)); + if (src_entry->copied) + { + copyfrom_url = apr_pstrdup(pool, src_entry->copyfrom_url); + copyfrom_rev = src_entry->copyfrom_rev; + } + else + SVN_ERR(svn_wc_get_ancestry(©from_url, ©from_rev, + src_path, src_access, pool)); /* Load source base and working props. */ SVN_ERR(svn_wc__load_props(&base_props, &props, src_access, @@ -348,9 +354,9 @@ (SVN_ERR_ENTRY_NOT_FOUND, NULL, _("'%s' is not under version control"), svn_path_local_style(src_path, pool)); - if ((src_entry->schedule == svn_wc_schedule_add) - || (! src_entry->url) - || (src_entry->copied)) + if (((src_entry->schedule == svn_wc_schedule_add) && + (!src_entry->copied)) + || (! src_entry->url)) return svn_error_createf (SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot copy or move '%s': it is not in the repository yet; " @@ -379,6 +385,16 @@ SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, dst_path, TRUE, -1, cancel_func, cancel_baton, pool)); SVN_ERR(post_copy_cleanup(adm_access, pool)); + + if (src_entry->copied && src_entry->copyfrom_url) + { + svn_wc_entry_t tmp_entry; + tmp_entry.url = apr_pstrdup(pool, src_entry->copyfrom_url); + SVN_ERR(svn_wc__entry_modify(adm_access, NULL, /* This Dir */ + &tmp_entry, SVN_WC__ENTRY_MODIFY_URL, + TRUE, pool)); + } + SVN_ERR(svn_wc_adm_close(adm_access)); /* Schedule the directory for addition in both its parent and itself @@ -388,9 +404,15 @@ char *copyfrom_url; svn_revnum_t copyfrom_rev; - SVN_ERR(svn_wc_get_ancestry(©from_url, ©from_rev, - src_path, src_access, pool)); - + if (src_entry->copied) + { + copyfrom_url = apr_pstrdup(pool, src_entry->copyfrom_url); + copyfrom_rev = src_entry->copyfrom_rev; + } + else + SVN_ERR(svn_wc_get_ancestry(©from_url, ©from_rev, + src_path, src_access, pool)); + SVN_ERR(svn_wc_add2(dst_path, dst_parent, copyfrom_url, copyfrom_rev, cancel_func, cancel_baton, Index: subversion/tests/cmdline/copy_tests.py =================================================================== --- subversion/tests/cmdline/copy_tests.py (revision 19715) +++ subversion/tests/cmdline/copy_tests.py (working copy) @@ -1942,6 +1942,329 @@ 'cat', svntest.main.current_repo_url + '/dest') + +def copy_copied_file_and_dir(sbox): + "copy a copied file and dir" + # Improve support for copy and move + # Allow copy of copied items without a commit between + + sbox.build() + wc_dir = sbox.wc_dir + + rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + rho_copy_path_1 = os.path.join(wc_dir, 'A', 'D', 'rho_copy_1') + rho_copy_path_2 = os.path.join(wc_dir, 'A', 'B', 'F', 'rho_copy_2') + + # Copy A/D/G/rho to A/D/rho_copy_1 + svntest.actions.run_and_verify_svn(None, None, [], 'cp', + rho_path, rho_copy_path_1) + + # Copy the copied file: A/D/rho_copy_1 to A/B/F/rho_copy_2 + svntest.actions.run_and_verify_svn(None, None, [], 'cp', + rho_copy_path_1, rho_copy_path_2) + + E_path = os.path.join(wc_dir, 'A', 'B', 'E') + E_path_copy_1 = os.path.join(wc_dir, 'A', 'B', 'F', 'E_copy_1') + E_path_copy_2 = os.path.join(wc_dir, 'A', 'D', 'G', 'E_copy_2') + + # Copy A/B/E to A/B/F/E_copy_1 + svntest.actions.run_and_verify_svn(None, None, [], 'cp', + E_path, E_path_copy_1) + + # Copy the copied dir: A/B/F/E_copy_1 to A/D/G/E_copy_2 + svntest.actions.run_and_verify_svn(None, None, [], 'cp', + E_path_copy_1, E_path_copy_2) + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/D/rho_copy_1' : Item(verb='Adding'), + 'A/B/F/rho_copy_2' : Item(verb='Adding'), + 'A/B/F/E_copy_1/' : Item(verb='Adding'), + 'A/D/G/E_copy_2/' : Item(verb='Adding'), + }) + + # Create expected status tree + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak(wc_rev=1) + expected_status.add({ + 'A/D/rho_copy_1' : Item(status=' ', wc_rev=2), + 'A/B/F/rho_copy_2' : Item(status=' ', wc_rev=2), + 'A/B/F/E_copy_1' : Item(status=' ', wc_rev=2), + 'A/B/F/E_copy_1/alpha' : Item(status=' ', wc_rev=2), + 'A/B/F/E_copy_1/beta' : Item(status=' ', wc_rev=2), + 'A/D/G/E_copy_2' : Item(status=' ', wc_rev=2), + 'A/D/G/E_copy_2/alpha' : Item(status=' ', wc_rev=2), + 'A/D/G/E_copy_2/beta' : Item(status=' ', wc_rev=2), + }) + + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + wc_dir) + + +def move_copied_file_and_dir(sbox): + "move a copied file and dir" + + sbox.build() + wc_dir = sbox.wc_dir + + rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + rho_copy_path = os.path.join(wc_dir, 'A', 'D', 'rho_copy') + rho_copy_move_path = os.path.join(wc_dir, 'A', 'B', 'F', 'rho_copy_moved') + + # Copy A/D/G/rho to A/D/rho_copy + svntest.actions.run_and_verify_svn(None, None, [], 'cp', + rho_path, rho_copy_path) + + # Move the copied file: A/D/rho_copy to A/B/F/rho_copy_moved + # + # The --force option is required we get this error without it: + # + # svn: Use --force to override this restriction + # svn: Move will not be attempted unless forced + # svn: 'svn-test-work\working_copies\copy_tests-1\A\D\rho_copy_1' + # has local modifications + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + rho_copy_path, rho_copy_move_path, + '--force') + + E_path = os.path.join(wc_dir, 'A', 'B', 'E') + E_path_copy = os.path.join(wc_dir, 'A', 'B', 'F', 'E_copy') + E_path_copy_move = os.path.join(wc_dir, 'A', 'D', 'G', 'E_copy_moved') + + # Copy A/B/E to A/B/F/E_copy + svntest.actions.run_and_verify_svn(None, None, [], 'cp', + E_path, E_path_copy) + + # Move the copied file: A/B/F/E_copy to A/D/G/E_copy_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + E_path_copy, E_path_copy_move, + '--force') + + # Created expected output tree for 'svn ci': + # Since we are moving items that were only *scheduled* for addition + # we expect only to additions when checking in, rather than a + # deletion/addition pair. + expected_output = svntest.wc.State(wc_dir, { + 'A/B/F/rho_copy_moved' : Item(verb='Adding'), + 'A/D/G/E_copy_moved/' : Item(verb='Adding'), + }) + + # Create expected status tree + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak(wc_rev=1) + expected_status.add({ + 'A/B/F/rho_copy_moved' : Item(status=' ', wc_rev=2), + 'A/D/G/E_copy_moved' : Item(status=' ', wc_rev=2), + 'A/D/G/E_copy_moved/alpha' : Item(status=' ', wc_rev=2), + 'A/D/G/E_copy_moved/beta' : Item(status=' ', wc_rev=2), + }) + + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + wc_dir) + + +def move_moved_file_and_dir(sbox): + "move a moved file and dir" + + sbox.build() + wc_dir = sbox.wc_dir + + rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + rho_move_path = os.path.join(wc_dir, 'A', 'D', 'rho_moved') + rho_move_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'rho_move_moved') + + # Move A/D/G/rho to A/D/rho_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + rho_path, rho_move_path) + + # Move the moved file: A/D/rho_moved to A/B/F/rho_move_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + rho_move_path, rho_move_moved_path, + '--force') + + E_path = os.path.join(wc_dir, 'A', 'B', 'E') + E_path_moved = os.path.join(wc_dir, 'A', 'B', 'F', 'E_moved') + E_path_move_moved = os.path.join(wc_dir, 'A', 'D', 'G', 'E_move_moved') + + # Copy A/B/E to A/B/F/E_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + E_path, E_path_moved) + + # Move the moved file: A/B/F/E_moved to A/D/G/E_move_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + E_path_moved, E_path_move_moved, + '--force') + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/B/E' : Item(verb='Deleting'), + 'A/D/G/E_move_moved/' : Item(verb='Adding'), + 'A/D/G/rho' : Item(verb='Deleting'), + 'A/B/F/rho_move_moved' : Item(verb='Adding'), + }) + + # Create expected status tree + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak(wc_rev=1) + expected_status.add({ + 'A/D/G/E_move_moved/' : Item(status=' ', wc_rev=2), + 'A/D/G/E_move_moved/alpha' : Item(status=' ', wc_rev=2), + 'A/D/G/E_move_moved/beta' : Item(status=' ', wc_rev=2), + 'A/B/F/rho_move_moved' : Item(status=' ', wc_rev=2), + }) + + expected_status.remove('A/B/E', + 'A/B/E/alpha', + 'A/B/E/beta', + 'A/D/G/rho') + + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + wc_dir) + + +def move_file_within_moved_dir(sbox): + "move a file within a moved dir" + + sbox.build() + wc_dir = sbox.wc_dir + + E_path = os.path.join(wc_dir, 'A', 'B', 'E') + E_path_moved = os.path.join(wc_dir, 'A', 'B', 'F', 'E_moved') + + # Move A/B/E to A/B/F/E_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + E_path, E_path_moved) + + alpha_path = os.path.join(wc_dir, 'A', 'B', 'F', 'E_moved', 'alpha') + alpha_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'E_moved', + 'alpha_moved') + alpha_moved_again_path = os.path.join(wc_dir, 'A', 'B', 'F', 'E_moved', + 'alpha_moved_again') + + # Move A/B/F/E_moved/alpha to A/B/F/E_moved/alpha_moved + # then move that to A/B/F/E_moved/alpha_moved_again + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + alpha_path, alpha_moved_path) + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + alpha_moved_path, + alpha_moved_again_path, + '--force') + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/B/E' : Item(verb='Deleting'), + 'A/B/F/E_moved/' : Item(verb='Adding'), + 'A/B/F/E_moved/alpha' : Item(verb='Deleting'), + 'A/B/F/E_moved/alpha_moved_again' : Item(verb='Adding'), + }) + + # Create expected status tree + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak(wc_rev=1) + expected_status.add({ + 'A/B/F/E_moved/' : Item(status=' ', wc_rev=2), + 'A/B/F/E_moved/alpha_moved_gain' : Item(status=' ', wc_rev=2), + 'A/B/F/E_moved/beta' : Item(status=' ', wc_rev=2), + }) + + expected_status.remove('A/B/E', + 'A/B/E/alpha', + 'A/B/E/beta', + ) + + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + wc_dir) + + +def move_dir_within_moved_dir(sbox): + "move a dir within a moved dir" + + sbox.build() + wc_dir = sbox.wc_dir + + D_path = os.path.join(wc_dir, 'A', 'D') + D_path_moved = os.path.join(wc_dir, 'A', 'B', 'D_moved') + + # Move A/D to A/B/D_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + D_path, D_path_moved) + + H_path = os.path.join(wc_dir, 'A', 'B', 'D_moved', 'H') + H_moved_path = os.path.join(wc_dir, 'A', 'B', 'D_moved', 'H_moved') + H_moved_again_path = os.path.join(wc_dir, 'A', 'B', 'D_moved', + 'H_moved_again') + + # Move dir A/B/D_moved/H to A/B/D_moved/H_moved + # then move that to A/B/D_moved/H_moved_again + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + H_path, H_moved_path) + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + H_moved_path, H_moved_again_path) + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/D' : Item(verb='Deleting'), + 'A/B/D_moved/' : Item(verb='Adding'), + 'A/B/D_moved/H' : Item(verb='Deleting'), + 'A/B/D_moved/H_moved_again' : Item(verb='Adding'), + }) + + # Create expected status tree + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak(wc_rev=1) + expected_status.add({ + 'A/B/D_moved' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/G' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/G/pi' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/G/rho' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/G/tau' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/gamma' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/H_moved_again' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/H_moved_again/chi' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/H_moved_again/omega' : Item(status=' ', wc_rev=2), + 'A/B/D_moved/H_moved_again/psi' : Item(status=' ', wc_rev=2), + }) + + expected_status.remove('A/D', + 'A/D/G', + 'A/D/H', + 'A/D/gamma', + 'A/D/G/pi', + 'A/D/G/rho', + 'A/D/G/tau', + 'A/D/H/chi', + 'A/D/H/omega', + 'A/D/H/psi', + ) + + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + wc_dir) + ######################################################################## # Run the tests @@ -1986,6 +2309,11 @@ mv_unversioned_file, force_move, copy_deleted_dir_into_prefix, + copy_copied_file_and_dir, + move_copied_file_and_dir, + move_moved_file_and_dir, + move_file_within_moved_dir, + move_dir_within_moved_dir ] if __name__ == '__main__':