Author: sbutler Date: Mon Jun 6 11:18:29 2011 New Revision: 1132598 URL: http://svn.apache.org/viewvc?rev=1132598&view=rev Log: A partial fix for issue 3899 "Auto-resolve conflicts at wc-wc copy/move destination". There are no longer any conflicts at copy destinations.
The test is XFAIL because text-conflicted files are copied as-is, with conflict-marker lines. They should be copied from the ".working" file, if it exists, on the principle that a local copy includes all local modifications. * subversion/libsvn_wc/wc-queries.sql (STMT_SELECT_CONFLICT_MARKER_FILES): New query. * subversion/libsvn_wc/copy.c (copy_versioned_dir): When copying unversioned filesystem children, skip any conflict-marker files. * subversion/libsvn_wc/wc_db.h (svn_wc__db_get_conflict_marker_files): New function. * subversion/libsvn_wc/wc_db.c (svn_wc__db_get_conflict_marker_files): New function. (copy_actual): New helper function. (cross_db_copy, db_op_copy): Use copy_actual. * subversion/tests/cmdline/copy_tests.py (): Import a few handy functions directly. (copying_conflicts): New XFAIL test. (test_list): Add the test. * subversion/tests/cmdline/svntest/sandbox.py (Sandbox.simple_move): New method. Modified: subversion/trunk/subversion/libsvn_wc/copy.c subversion/trunk/subversion/libsvn_wc/wc-queries.sql subversion/trunk/subversion/libsvn_wc/wc_db.c subversion/trunk/subversion/libsvn_wc/wc_db.h subversion/trunk/subversion/tests/cmdline/copy_tests.py subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py Modified: subversion/trunk/subversion/libsvn_wc/copy.c URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/copy.c?rev=1132598&r1=1132597&r2=1132598&view=diff ============================================================================== --- subversion/trunk/subversion/libsvn_wc/copy.c (original) +++ subversion/trunk/subversion/libsvn_wc/copy.c Mon Jun 6 11:18:29 2011 @@ -482,10 +482,16 @@ copy_versioned_dir(svn_wc__db_t *db, } } - /* Copy all the remaining filesystem children, which are unversioned. */ + /* Copy the remaining filesystem children, which are unversioned, skipping + any conflict-marker files. */ if (disk_children) { apr_hash_index_t *hi; + apr_hash_t *marker_files; + + SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db, + src_abspath, scratch_pool, + scratch_pool)); for (hi = apr_hash_first(scratch_pool, disk_children); hi; hi = apr_hash_next(hi)) @@ -497,6 +503,10 @@ copy_versioned_dir(svn_wc__db_t *db, if (svn_wc_is_adm_dir(name, iterpool)) continue; + if (marker_files && + apr_hash_get(marker_files, name, APR_HASH_KEY_STRING)) + continue; + svn_pool_clear(iterpool); if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); Modified: subversion/trunk/subversion/libsvn_wc/wc-queries.sql URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/wc-queries.sql?rev=1132598&r1=1132597&r2=1132598&view=diff ============================================================================== --- subversion/trunk/subversion/libsvn_wc/wc-queries.sql (original) +++ subversion/trunk/subversion/libsvn_wc/wc-queries.sql Mon Jun 6 11:18:29 2011 @@ -695,6 +695,13 @@ WHERE wc_id = ?1 AND parent_relpath = ?2 AND (conflict_new IS NULL) AND (conflict_working IS NULL) AND (tree_conflict_data IS NULL)) +-- STMT_SELECT_CONFLICT_MARKER_FILES +SELECT prop_reject, conflict_old, conflict_new, conflict_working +FROM actual_node +WHERE wc_id = ?1 AND (local_relpath = ?2 OR parent_relpath = ?2) + AND ((prop_reject IS NOT NULL) OR (conflict_old IS NOT NULL) + OR (conflict_new IS NOT NULL) OR (conflict_working IS NOT NULL)) + -- STMT_SELECT_ACTUAL_CHILDREN_TREE_CONFLICT SELECT local_relpath, tree_conflict_data FROM actual_node Modified: subversion/trunk/subversion/libsvn_wc/wc_db.c URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/wc_db.c?rev=1132598&r1=1132597&r2=1132598&view=diff ============================================================================== --- subversion/trunk/subversion/libsvn_wc/wc_db.c (original) +++ subversion/trunk/subversion/libsvn_wc/wc_db.c Mon Jun 6 11:18:29 2011 @@ -3172,6 +3172,52 @@ svn_wc__db_externals_gather_definitions( svn_sqlite__reset(stmt))); } +/* Copy the ACTUAL data for SRC_RELPATH and tweak it to refer to DST_RELPATH. + The new ACTUAL data won't have any conflicts. */ +static svn_error_t * +copy_actual(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + apr_size_t props_size; + const char *changelist; + const char *properties; + + /* Skipping conflict data... */ + changelist = svn_sqlite__column_text(stmt, 1, scratch_pool); + /* No need to parse the properties when simply copying. */ + properties = svn_sqlite__column_blob(stmt, 6, &props_size, scratch_pool); + + if (changelist || properties) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, + STMT_INSERT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "issbssssss", + dst_wcroot->wc_id, dst_relpath, + svn_relpath_dirname(dst_relpath, scratch_pool), + properties, props_size, NULL, NULL, NULL, + NULL, changelist, NULL)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + /* Helper for svn_wc__db_op_copy to handle copying from one db to another */ static svn_error_t * @@ -3195,8 +3241,6 @@ cross_db_copy(svn_wc__db_wcroot_t *src_w const char *changed_author; const svn_checksum_t *checksum; apr_hash_t *props; - svn_sqlite__stmt_t *stmt; - svn_boolean_t have_row; svn_depth_t depth; SVN_ERR_ASSERT(kind == svn_wc__db_kind_file @@ -3235,62 +3279,8 @@ cross_db_copy(svn_wc__db_wcroot_t *src_w SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, scratch_pool)); - SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, - STMT_SELECT_ACTUAL_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - if (have_row) - { - /* ### STMT_INSERT_ACTUAL_NODE doesn't cover every column, it's - ### enough for some cases but will probably need to extended. */ - const char *prop_reject = svn_sqlite__column_text(stmt, 0, scratch_pool); - const char *changelist = svn_sqlite__column_text(stmt, 1, scratch_pool); - const char *conflict_old = svn_sqlite__column_text(stmt, 2, scratch_pool); - const char *conflict_new = svn_sqlite__column_text(stmt, 3, scratch_pool); - const char *conflict_wrk = svn_sqlite__column_text(stmt, 4, scratch_pool); - const char *tree_conflict_data = svn_sqlite__column_text(stmt, 5, - scratch_pool); - apr_size_t props_size; - /* No need to parse the properties when simply copying. */ - const char *properties = svn_sqlite__column_blob(stmt, 6, &props_size, - scratch_pool); - - SVN_ERR(svn_sqlite__reset(stmt)); - - if (prop_reject) - prop_reject = svn_relpath_join(dst_relpath, - svn_relpath_skip_ancestor(src_relpath, - prop_reject), - scratch_pool); - if (conflict_old) - conflict_old = svn_relpath_join(dst_relpath, - svn_relpath_skip_ancestor(src_relpath, - conflict_old), - scratch_pool); - if (conflict_new) - conflict_new = svn_relpath_join(dst_relpath, - svn_relpath_skip_ancestor(src_relpath, - conflict_new), - scratch_pool); - if (conflict_wrk) - conflict_wrk = svn_relpath_join(dst_relpath, - svn_relpath_skip_ancestor(src_relpath, - conflict_wrk), - scratch_pool); - - /* ### Do we need to adjust relpaths in tree conflict data? */ - - SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, - STMT_INSERT_ACTUAL_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "issbssssss", - dst_wcroot->wc_id, dst_relpath, - svn_relpath_dirname(dst_relpath, scratch_pool), - properties, props_size, - conflict_old, conflict_new, conflict_wrk, - prop_reject, changelist, tree_conflict_data)); - SVN_ERR(svn_sqlite__step(&have_row, stmt)); - } - SVN_ERR(svn_sqlite__reset(stmt)); + SVN_ERR(copy_actual(src_wcroot, src_relpath, + dst_wcroot, dst_relpath, scratch_pool)); return SVN_NO_ERROR; } @@ -3563,11 +3553,8 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcro SVN_ERR(svn_sqlite__step_done(stmt)); /* ### Copying changelist is OK for a move but what about a copy? */ - SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, - STMT_INSERT_ACTUAL_NODE_FROM_ACTUAL_NODE)); - SVN_ERR(svn_sqlite__bindf(stmt, "isss", src_wcroot->wc_id, src_relpath, - dst_relpath, dst_parent_relpath)); - SVN_ERR(svn_sqlite__step_done(stmt)); + SVN_ERR(copy_actual(src_wcroot, src_relpath, + dst_wcroot, dst_relpath, scratch_pool)); if (dst_np_op_depth > 0) { @@ -10127,6 +10114,59 @@ svn_wc__db_read_conflict_victims(const a svn_error_t * +svn_wc__db_get_conflict_marker_files(apr_hash_t **marker_files, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Look for text and property conflicts in ACTUAL */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CONFLICT_MARKER_FILES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + *marker_files = apr_hash_make(result_pool); + else + *marker_files = NULL; + + while (have_row) + { + /* Collect the basenames of any conflict marker files. */ + const char *marker_relpath; + const char *basename; + int i; + + for (i = 0; i < 4; i++) + { + marker_relpath = svn_sqlite__column_text(stmt, i, scratch_pool); + if (marker_relpath) + { + basename = svn_dirent_basename(marker_relpath, result_pool); + apr_hash_set(*marker_files, basename, APR_HASH_KEY_STRING, + basename); + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + return svn_sqlite__reset(stmt); +} + + +svn_error_t * svn_wc__db_read_conflicts(const apr_array_header_t **conflicts, svn_wc__db_t *db, const char *local_abspath, Modified: subversion/trunk/subversion/libsvn_wc/wc_db.h URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/wc_db.h?rev=1132598&r1=1132597&r2=1132598&view=diff ============================================================================== --- subversion/trunk/subversion/libsvn_wc/wc_db.h (original) +++ subversion/trunk/subversion/libsvn_wc/wc_db.h Mon Jun 6 11:18:29 2011 @@ -2062,6 +2062,20 @@ svn_wc__db_read_conflict_victims(const a apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Read into *MARKER_FILES the basenames of the immediate children of + LOCAL_ABSPATH in DB that are unversioned marker files for text or + property conflicts. The files may have been deleted by the user. + + Allocate *MARKER_FILES in RESULT_POOL and do temporary allocations + in SCRATCH_POOL */ +/* ### This function will probably be removed. */ +svn_error_t * +svn_wc__db_get_conflict_marker_files(apr_hash_t **markers, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Read into CONFLICTS svn_wc_conflict_description2_t* structs for all conflicts that have LOCAL_ABSPATH as victim. Modified: subversion/trunk/subversion/tests/cmdline/copy_tests.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/copy_tests.py?rev=1132598&r1=1132597&r2=1132598&view=diff ============================================================================== --- subversion/trunk/subversion/tests/cmdline/copy_tests.py (original) +++ subversion/trunk/subversion/tests/cmdline/copy_tests.py Mon Jun 6 11:18:29 2011 @@ -30,7 +30,13 @@ import stat, os, re, shutil # Our testing module import svntest from svntest import main -from svntest.main import SVN_PROP_MERGEINFO +from svntest.main import ( + SVN_PROP_MERGEINFO, + file_append, + file_write, + make_log_msg, + run_svn, +) # (abbreviation) Skip = svntest.testcase.Skip_deco @@ -5214,7 +5220,169 @@ def case_only_rename(sbox): # Test that the necessary deletes and adds are present in status. svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@XFail() +@Issue(3899) +def copying_conflicts(sbox): + """copying conflicts""" + + # The destination of a copy operation should *not* be conflicted, + # and should contain the "mine-full" contents. + + sbox.build() + wc = sbox.ospath + def url(relpath): + return '/'.join([sbox.repo_url, relpath]) + + # Create an assortment of conflicts. + # text A/B/E/alpha + # property (dir) A/D/H + # property (file) A/D/H/chi + # tree: local delete, incoming edit A/D/gamma + # tree: local edit, incoming delete A/D/G + # tree: local add, incoming add A/Q + # tree: local missing, incoming edit A/B/E/sigma + + ### As we improve tree-conflict handling, this test may need some + ### maintenance. + + # Create a branch for merging. + run_svn(None, 'cp', url('A'), url('A2'), '-m', make_log_msg()) # r2 + sbox.simple_update() + + # This revision won't be included in the merge, producing a "local + # missing" tree conflict. + file_write(wc('A2/B/E/sigma'), "New for merge.\n") + sbox.simple_add('A2/B/E/sigma') + + sbox.simple_commit('A2') # r3 + + # Make "incoming" changes in A2 for the merge + # incoming edits + file_append(wc('A2/B/E/alpha'), "Edit for merge\n") + file_append(wc('A2/B/E/sigma'), "Edit for merge\n") + sbox.simple_propset('foo', '99', 'A2/D/H') + sbox.simple_propset('foo', '99', 'A2/D/H/chi') + # incoming add + sbox.simple_mkdir('A2/Q') + file_write(wc('A2/Q/zeta'), "New for merge\n") + sbox.simple_add('A2/Q/zeta') + sbox.simple_commit('A2') # r4 + + # Make some "local" changes in A before the merge. + # local edit + file_append(wc('A/B/E/alpha'), "Local edit\n") + sbox.simple_propset('foo', '100', 'A/D/H') + sbox.simple_propset('foo', '100', 'A/D/H/chi') + # local add + sbox.simple_mkdir('A/Q') + file_write(wc('A/Q/sigma'), "New local file\n") + sbox.simple_add('A/Q/sigma') + + # Make some "incoming" changes in A before the update. + # incoming edit + file_append(wc('A/D/gamma'), "Edit for merge\n") + # incoming delete + sbox.simple_rm('A/D/G') + + sbox.simple_commit('A') # r5 + + # Roll back, make local, uncommitted changes. + run_svn(None, 'up', '-r', 4, sbox.wc_dir) + # local delete + sbox.simple_rm('A/D/gamma') + # local edit + file_append(wc('A/D/G/rho'), "Local edit\n") + + # Update to reveal the "local {delete,edit'}" tree conflicts, + # which we can't yet catch when merging. + sbox.simple_update() + + # Merge just one revision to reveal more conflicts. + run_svn(None, 'merge', '-c', 4, url('A2'), wc('A')) + + # Prepare for local copies and moves. + sbox.simple_mkdir('copy-dest') + sbox.simple_mkdir('move-dest') + + # Copy conflict victims. + sbox.simple_copy('A/B/E/alpha', 'copy-dest') + sbox.simple_copy('A/D/H', 'copy-dest') + sbox.simple_copy('A/D/G', 'copy-dest') + sbox.simple_copy('A/Q', 'copy-dest') + + # Copy directories with conflicted children. + sbox.simple_copy('A/B', 'copy-dest') + sbox.simple_copy('A/D', 'copy-dest') + + # Everything copied without conflicts. The entry_status for D/G is + # for 1.6 compatibility (see notes/api-errata/1.7/wc003.xt). + expected_status = svntest.wc.State(wc('copy-dest'), { + '' : Item(status='A ', wc_rev=0), + 'B' : Item(status='A ', copied='+', wc_rev='-'), + 'B/E' : Item(status=' ', copied='+', wc_rev='-'), + 'B/E/alpha' : Item(status='M ', copied='+', wc_rev='-'), + 'B/E/beta' : Item(status=' ', copied='+', wc_rev='-'), + 'B/F' : Item(status=' ', copied='+', wc_rev='-'), + 'B/lambda' : Item(status=' ', copied='+', wc_rev='-'), + 'D' : Item(status='A ', copied='+', wc_rev='-'), + 'D/G' : Item(status='A ', copied='+', wc_rev='-', + entry_status=' '), + 'D/G/pi' : Item(status=' ', copied='+', wc_rev='-'), + 'D/G/rho' : Item(status='M ', copied='+', wc_rev='-'), + 'D/G/tau' : Item(status=' ', copied='+', wc_rev='-'), + 'D/H' : Item(status=' ', copied='+', wc_rev='-'), + 'D/H/chi' : Item(status=' ', copied='+', wc_rev='-'), + 'D/H/omega' : Item(status=' ', copied='+', wc_rev='-'), + 'D/H/psi' : Item(status=' ', copied='+', wc_rev='-'), + 'D/gamma' : Item(status='D ', copied='+', wc_rev='-'), + 'G' : Item(status='A ', copied='+', wc_rev='-'), + 'G/pi' : Item(status=' ', copied='+', wc_rev='-'), + 'G/rho' : Item(status='M ', copied='+', wc_rev='-'), + 'G/tau' : Item(status=' ', copied='+', wc_rev='-'), + 'H' : Item(status='A ', copied='+', wc_rev='-'), + 'H/chi' : Item(status=' ', copied='+', wc_rev='-'), + 'H/omega' : Item(status=' ', copied='+', wc_rev='-'), + 'H/psi' : Item(status=' ', copied='+', wc_rev='-'), + 'Q' : Item(status='A ', copied='+', wc_rev='-'), + 'Q/sigma' : Item(status=' ', copied='+', wc_rev='-'), + 'alpha' : Item(status='A ', copied='+', wc_rev='-'), + }) + svntest.actions.run_and_verify_status(wc('copy-dest'), expected_status) + + # Only the local changes remain at the copy destinations. + ### Currently fails because alpha and B/E/alpha contain conflict-marker text. + expected_disk = svntest.wc.State('', { + 'B/E/alpha' : Item(contents="This is the file 'alpha'.\n" + "Local edit\n"), + 'B/E/beta' : Item(contents="This is the file 'beta'.\n"), + 'B/F' : Item(), + 'B/lambda' : Item(contents="This is the file 'lambda'.\n"), + 'D/G/pi' : Item(contents="This is the file 'pi'.\n"), + 'D/G/rho' : Item(contents="This is the file 'rho'.\n" + "Local edit\n"), + 'D/G/tau' : Item(contents="This is the file 'tau'.\n"), + 'D/H' : Item(props={'foo':'100'}), + 'D/H/chi' : Item(contents="This is the file 'chi'.\n", + props={'foo':'100'}), + 'D/H/omega' : Item(contents="This is the file 'omega'.\n"), + 'D/H/psi' : Item(contents="This is the file 'psi'.\n"), + 'G/pi' : Item(contents="This is the file 'pi'.\n"), + 'G/rho' : Item(contents="This is the file 'rho'.\n" + "Local edit\n"), + 'G/tau' : Item(contents="This is the file 'tau'.\n"), + 'H' : Item(props={'foo':'100'}), + 'H/chi' : Item(contents="This is the file 'chi'.\n", + props={'foo':'100'}), + 'H/omega' : Item(contents="This is the file 'omega'.\n"), + 'H/psi' : Item(contents="This is the file 'psi'.\n"), + 'Q/sigma' : Item(contents="New local file\n"), + 'alpha' : Item(contents="This is the file 'alpha'.\n" + "Local edit\n"), + }) + svntest.actions.verify_disk(wc('copy-dest'), expected_disk, True) + ######################################################################## # Run the tests @@ -5324,6 +5492,7 @@ test_list = [ None, deleted_file_with_case_clash, copy_base_of_deleted, case_only_rename, + copying_conflicts, ] if __name__ == '__main__': Modified: subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py?rev=1132598&r1=1132597&r2=1132598&view=diff ============================================================================== --- subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py (original) +++ subversion/trunk/subversion/tests/cmdline/svntest/sandbox.py Mon Jun 6 11:18:29 2011 @@ -258,6 +258,12 @@ class Sandbox: dest = self.ospath(dest) svntest.main.run_svn(False, 'copy', source, dest) + def simple_move(self, source, dest): + """SOURCE and DEST are relpaths relative to the WC.""" + source = self.ospath(source) + dest = self.ospath(dest) + svntest.main.run_svn(False, 'move', source, dest) + def simple_repo_copy(self, source, dest): """SOURCE and DEST are relpaths relative to the repo root.""" svntest.main.run_svn(False, 'copy', '-m', svntest.main.make_log_msg(),