Index: subversion/libsvn_wc/copy.c =================================================================== --- subversion/libsvn_wc/copy.c (revision 20707) +++ subversion/libsvn_wc/copy.c (working copy) @@ -39,6 +39,70 @@ /*** Code. ***/ +/* Helper function for copy_file_administratively() and + copy_dir_administratively(). Determines the COPYFROM_URL and + COPYFROM_REV of a file or directory SRC_PATH which is the descendant + of an explicitly moved or copied directory that has not been committed. +*/ +static svn_error_t * +get_copyfrom_url_rev_via_parent(const char *src_path, + const char **copyfrom_url, + svn_revnum_t *copyfrom_rev, + svn_wc_adm_access_t *src_access, + apr_pool_t *pool) +{ + const char *parent_path = svn_path_dirname(src_path, pool); + const char *rest = svn_path_basename(src_path, pool); + *copyfrom_url = NULL; + + while (! *copyfrom_url) + { + svn_wc_adm_access_t *parent_access; + const svn_wc_entry_t *entry; + + /* Don't look for parent_path in src_access if it can't be + there... */ + if (svn_path_is_ancestor(svn_wc_adm_access_path(src_access), + parent_path)) + { + SVN_ERR(svn_wc_adm_retrieve(&parent_access, src_access, + parent_path, pool)); + SVN_ERR(svn_wc_entry(&entry, parent_path, parent_access, + FALSE, pool)); + } + else /* ...get access for parent_path instead. */ + { + SVN_ERR(svn_wc_adm_probe_open3(&parent_access, NULL, + parent_path, FALSE, -1, + NULL, NULL, pool)); + SVN_ERR(svn_wc_entry(&entry, parent_path, parent_access, + FALSE, pool)); + SVN_ERR(svn_wc_adm_close(parent_access)); + } + + if (! entry) + return svn_error_createf + (SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_path_local_style(parent_path, pool)); + + if (entry->copyfrom_url) + { + *copyfrom_url = svn_path_join(entry->copyfrom_url, rest, + pool); + *copyfrom_rev = entry->copyfrom_rev; + } + else + { + rest = svn_path_join(svn_path_basename(parent_path, pool), + rest, pool); + parent_path = svn_path_dirname(parent_path, pool); + } + } + + return SVN_NO_ERROR; +} + /* This function effectively creates and schedules a file for addition, but does extra administrative things to allow it to function as a 'copy'. @@ -90,18 +154,19 @@ svn_path_local_style(dst_path, pool)); } - /* Sanity check: you cannot make a copy of something that's not - in the repository. See comment at the bottom of this file for an - explanation. */ + /* Sanity check 1: You cannot make a copy of something that's not + under version control. */ SVN_ERR(svn_wc_entry(&src_entry, src_path, src_access, FALSE, pool)); if (! src_entry) return svn_error_createf (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)) + + /* Sanity check 2: You cannot make a copy of something that's not + in the repository unless it's a copy of an uncommitted copy. */ + 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 +181,30 @@ 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)); + /* Are we moving or copying a file that is already moved or copied + but not committed? */ + if (src_entry->copied) + { + if (src_entry->copyfrom_url) + { + /* When copying/moving a file that was already explicitly + copied/moved then we know the URL it was copied from... */ + copyfrom_url = apr_pstrdup(pool, src_entry->copyfrom_url); + copyfrom_rev = src_entry->copyfrom_rev; + } + else + { + /* ...But if this file is merely the descendant of an explicitly + copied/moved directory, we need to do a bit more work to + determine copyfrom_url and copyfrom_rev. */ + SVN_ERR(get_copyfrom_url_rev_via_parent(src_path, ©from_url, + ©from_rev, + src_access, pool)); + } + } + 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, @@ -339,18 +426,19 @@ const char *dst_path = svn_path_join(svn_wc_adm_access_path(dst_parent), dst_basename, pool); - /* Sanity check: you cannot make a copy of something that's not - in the repository. See comment at the bottom of this file for an - explanation. */ + /* Sanity check 1: You cannot make a copy of something that's not + under version control. */ SVN_ERR(svn_wc_entry(&src_entry, src_path, src_access, FALSE, pool)); if (! src_entry) return svn_error_createf (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)) + + /* Sanity check 2: You cannot make a copy of something that's not + in the repository unless it's a copy of an uncommitted copy. */ + 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,7 +467,6 @@ 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)); - SVN_ERR(svn_wc_adm_close(adm_access)); /* Schedule the directory for addition in both its parent and itself (this_dir) -- WITH HISTORY. This function should leave the @@ -387,10 +474,43 @@ { char *copyfrom_url; svn_revnum_t copyfrom_rev; - - SVN_ERR(svn_wc_get_ancestry(©from_url, ©from_rev, - src_path, src_access, pool)); - + svn_wc_entry_t tmp_entry; + + /* Are we copying a dir that is already copied but not committed? */ + if (src_entry->copied) + { + if (src_entry->copyfrom_url) + { + /* When copying/moving a dir that was already explicitly + copied/moved then we know the URL it was copied from... */ + copyfrom_url = apr_pstrdup(pool, src_entry->copyfrom_url); + copyfrom_rev = src_entry->copyfrom_rev; + } + else + { + /* ...But if this dir is merely the descendant of an explicitly + copied/moved directory, we need to do a bit more work to + determine copyfrom_url and copyfrom_rev. */ + SVN_ERR(get_copyfrom_url_rev_via_parent(src_path, ©from_url, + ©from_rev, + src_access, pool)); + } + + /* The URL for a copied dir won't exist in the repository, which + will cause svn_wc_add2() below to fail. Set the URL to the + URL of the first copy for now to prevent this. */ + tmp_entry.url = apr_pstrdup(pool, copyfrom_url); + SVN_ERR(svn_wc__entry_modify(adm_access, NULL, /* This Dir */ + &tmp_entry, + SVN_WC__ENTRY_MODIFY_URL, TRUE, + pool)); + } + else + SVN_ERR(svn_wc_get_ancestry(©from_url, ©from_rev, + src_path, src_access, pool)); + + SVN_ERR(svn_wc_adm_close(adm_access)); + SVN_ERR(svn_wc_add2(dst_path, dst_parent, copyfrom_url, copyfrom_rev, cancel_func, cancel_baton, @@ -491,30 +611,4 @@ &nb, pool); } - -/* - Rabbinic Commentary - - - Q: Why can't we 'svn cp' something that we just copied? - i.e. 'svn cp foo foo2; svn cp foo2 foo3" - - A: It leads to inconsistencies. - - In the example above, foo2 has no associated repository URL, - because it hasn't been committed yet. But suppose foo3 simply - inherited foo's URL (i.e. foo3 'pointed' to foo as a copy - ancestor by virtue of transitivity.) - - For one, this is not what the user would expect. That's - certainly not what the user typed! Second, suppose that the - user did a commit between the two 'svn cp' commands. Now foo3 - really *would* point to foo2, but without that commit, it - pointed to foo. Ugly inconsistency, and the user has no idea - that foo3's ancestor would be different in each case. - - And even if somehow we *could* make foo3 point to foo2 before - foo2 existed in the repository... what's to prevent a user from - committing foo3 first? That would break. - -*/ + \ No newline at end of file Index: subversion/tests/cmdline/copy_tests.py =================================================================== --- subversion/tests/cmdline/copy_tests.py (revision 20707) +++ subversion/tests/cmdline/copy_tests.py (working copy) @@ -1885,6 +1885,552 @@ '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 twice 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', 'F', 'D_moved') + + # Move A/B/D to A/B/F/D_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + D_path, D_path_moved) + + chi_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved', 'H', 'chi') + chi_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved', + 'H', 'chi_moved') + chi_moved_again_path = os.path.join(wc_dir, 'A', 'B', 'F', + 'D_moved', 'H', 'chi_moved_again') + + # Move A/B/F/D_moved/H/chi to A/B/F/D_moved/H/chi_moved + # then move that to A/B/F/D_moved/H/chi_moved_again + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + chi_path, chi_moved_path) + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + chi_moved_path, + chi_moved_again_path, + '--force') + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/B/F/D_moved/' : Item(verb='Adding'), + 'A/B/F/D_moved/H/chi' : Item(verb='Deleting'), + 'A/B/F/D_moved/H/chi_moved_again' : Item(verb='Adding'), + 'A/D' : Item(verb='Deleting'), + }) + + # 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/D_moved' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/gamma' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/pi' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/rho' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/tau' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H/omega' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H/psi' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H/chi_moved_again' : Item(status=' ', wc_rev=2), + }) + + expected_status.remove('A/D', + 'A/D/gamma', + 'A/D/G', + 'A/D/G/pi', + 'A/D/G/rho', + 'A/D/G/tau', + 'A/D/H', + '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) + + +def move_file_out_of_moved_dir(sbox): + "move a file out of 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', 'F', 'D_moved') + + # Move A/B/D to A/B/F/D_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + D_path, D_path_moved) + + chi_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved', 'H', 'chi') + chi_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved', + 'H', 'chi_moved') + chi_moved_again_path = os.path.join(wc_dir, 'A', 'C', 'chi_moved_again') + + # Move A/B/F/D_moved/H/chi to A/B/F/D_moved/H/chi_moved + # then move that to A/C/chi_moved_again + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + chi_path, chi_moved_path) + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + chi_moved_path, + chi_moved_again_path, + '--force') + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/B/F/D_moved/' : Item(verb='Adding'), + 'A/B/F/D_moved/H/chi' : Item(verb='Deleting'), + 'A/C/chi_moved_again' : Item(verb='Adding'), + 'A/D' : Item(verb='Deleting'), + }) + + # 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/D_moved' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/gamma' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/pi' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/rho' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/tau' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H/omega' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H/psi' : Item(status=' ', wc_rev=2), + 'A/C/chi_moved_again' : Item(status=' ', wc_rev=2), + }) + + expected_status.remove('A/D', + 'A/D/gamma', + 'A/D/G', + 'A/D/G/pi', + 'A/D/G/rho', + 'A/D/G/tau', + 'A/D/H', + '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) + + +def move_dir_within_moved_dir(sbox): + "move a dir twice 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', 'F', 'D_moved') + + # Move A/D to A/B/F/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', 'F', 'D_moved', 'H') + H_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved', 'H_moved') + H_moved_again_path = os.path.join(wc_dir, 'A', 'B', 'F', + 'D_moved', 'H_moved_again') + + # Move A/B/F/D_moved/H to A/B/F/D_moved/H_moved + # then move that to A/B/F/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, + '--force') + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/D' : Item(verb='Deleting'), + 'A/B/F/D_moved' : Item(verb='Adding'), + 'A/B/F/D_moved/H' : Item(verb='Deleting'), + 'A/B/F/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/F/D_moved' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/gamma' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/pi' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/rho' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/tau' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H_moved_again' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H_moved_again/omega' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H_moved_again/psi' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/H_moved_again/chi' : Item(status=' ', wc_rev=2), + }) + + expected_status.remove('A/D', + 'A/D/gamma', + 'A/D/G', + 'A/D/G/pi', + 'A/D/G/rho', + 'A/D/G/tau', + 'A/D/H', + '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) + + +def move_dir_out_of_moved_dir(sbox): + "move a dir out of 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', 'F', 'D_moved') + + # Move A/D to A/B/F/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', 'F', 'D_moved', 'H') + H_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved', 'H_moved') + H_moved_again_path = os.path.join(wc_dir, 'A', 'C', 'H_moved_again') + + # Move A/B/F/D_moved/H to A/B/F/D_moved/H_moved + # then move that to A/C/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, + '--force') + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/D' : Item(verb='Deleting'), + 'A/B/F/D_moved' : Item(verb='Adding'), + 'A/B/F/D_moved/H' : Item(verb='Deleting'), + 'A/C/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/F/D_moved' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/gamma' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/pi' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/rho' : Item(status=' ', wc_rev=2), + 'A/B/F/D_moved/G/tau' : Item(status=' ', wc_rev=2), + 'A/C/H_moved_again' : Item(status=' ', wc_rev=2), + 'A/C/H_moved_again/omega' : Item(status=' ', wc_rev=2), + 'A/C/H_moved_again/psi' : Item(status=' ', wc_rev=2), + 'A/C/H_moved_again/chi' : Item(status=' ', wc_rev=2), + }) + + expected_status.remove('A/D', + 'A/D/gamma', + 'A/D/G', + 'A/D/G/pi', + 'A/D/G/rho', + 'A/D/G/tau', + 'A/D/H', + '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) + +def move_file_back_and_forth(sbox): + "move a moved file back to original location" + + 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') + + # Move A/D/G/rho to A/D/rho_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + rho_path, rho_move_path) + raise svntest.Failure + # 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_path, + '--force') + + # Created expected output tree for 'svn ci': + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G/rho' : Item(verb='Replacing'), + }) + + # 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/rho' : 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_dir_back_and_forth(sbox): + "move a moved dir back to original location" + + sbox.build() + wc_dir = sbox.wc_dir + + D_path = os.path.join(wc_dir, 'A', 'D') + D_move_path = os.path.join(wc_dir, 'D_moved') + + # Move A/D to D_moved + svntest.actions.run_and_verify_svn(None, None, [], 'mv', + D_path, D_move_path) + + # Move the moved dir: D_moved back to it's starting + # location at A/D. + out, err = svntest.actions.run_and_verify_svn(None, None, SVNAnyOutput, + 'mv', D_move_path, + D_path, '--force') + + for line in err: + if re.match('.*Cannot copy to .*as it is scheduled for deletion', + line, ): + return + raise svntest.Failure("mv failed but not in the expected way") + ######################################################################## # Run the tests @@ -1928,6 +2474,15 @@ 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_file_out_of_moved_dir, + move_dir_within_moved_dir, + move_dir_out_of_moved_dir, + move_file_back_and_forth, + move_dir_back_and_forth, ] if __name__ == '__main__':