When a tree conflict occurs on a directory, skip the processing of everything within that directory tree. ### Work in progress. Tests failing (at least commit, update, switch tests). * subversion/libsvn_wc/update_editor.c (edit_baton, dir_baton): Add a flag telling whether we're inside a conflict. (maybe_bump_dir_info): Skip if inside a conflict. (delete_entry): Skip if inside a conflict. (add_directory): Skip if inside a conflict. (conflicted_p3): New function, like I want svn_wc_conflicted_p2 to become. (open_directory): When raising a tree conflict, remember that we're inside it, (change_dir_prop): Skip if inside a tree conflict. (close_directory): Skip notification if inside a tree conflict. Clear the "inside a tree conflict" flag if this is the directory where we set it. (open_file): Skip if we get a tree conflict. (apply_textdelta, change_file_prop, close_file): Skip if inside a conflict. ### The "inside_a_tree_conflict" flag is presently a counter, but it should ### only need to count to "1" when the skipping works properly. * subversion/tests/cmdline/svntest/actions.py (deep_trees_after_leaf_del, deep_trees_after_tree_del): Adjust the expected state after generating tree conflicts. * subversion/tests/cmdline/commit_tests.py (tree_conflicts_resolved): * subversion/tests/cmdline/switch_tests.py (tree_conflicts_on_switch_1_1): Adjust expectations. (tree_conflicts_on_switch_1_2): Adjust expectations. (tree_conflicts_on_switch_2_2): Adjust expectations. * subversion/tests/cmdline/update_tests.py (tree_conflicts_sandbox): New test, for experimenting manually. (test_list): Add the new test. ### Test expectations are not matching the code. Index: subversion/libsvn_wc/update_editor.c =================================================================== --- subversion/libsvn_wc/update_editor.c (revision 33895) +++ subversion/libsvn_wc/update_editor.c (working copy) @@ -144,6 +144,10 @@ struct edit_baton The keys are pathnames and the values unspecified. */ apr_hash_t *skipped_paths; + /* Are we editing inside a sub-tree that is a tree conflict victim? (If + so, we will want to avoid actually changing anything.) */ + int /*svn_boolean_t*/ inside_a_tree_conflict; + apr_pool_t *pool; }; @@ -203,6 +207,11 @@ struct dir_baton (In other words, are we resuming an interrupted update?) */ svn_boolean_t was_incomplete; + /* If this directory is a tree conflict victim, this is the conflict + description. (If so, we will want to + avoid actually changing anything until we close this directory.) */ + svn_wc_conflict_description_t *tree_conflict; + /* The pool in which this baton itself is allocated. */ apr_pool_t *pool; }; @@ -583,7 +592,7 @@ maybe_bump_dir_info(struct edit_baton *e /* The refcount is zero, so we remove any 'dead' entries from the directory and mark it 'complete'. */ - if (! bdi->skipped) + if (! (bdi->skipped || eb->inside_a_tree_conflict)) SVN_ERR(complete_directory(eb, bdi->path, bdi->parent ? FALSE : TRUE, pool)); } @@ -1322,6 +1331,36 @@ check_tree_conflict(svn_wc_conflict_desc return SVN_NO_ERROR; } +/* Send a notification, via the notifier in the edit baton EB, of a tree + * conflict being raised with the description CONFLICT. */ +static void +notify_new_tree_conflict(const struct edit_baton *eb, + const svn_wc_conflict_description_t *conflict, + apr_pool_t *pool) +{ + svn_wc_notify_action_t action; + svn_wc_notify_t *notify; + + switch (conflict->action) + { + case svn_wc_conflict_action_edit: + action = svn_wc_notify_update_update; + break; + case svn_wc_conflict_action_add: + action = svn_wc_notify_update_update; + break; + case svn_wc_conflict_action_delete: + action = svn_wc_notify_update_update; + break; + } + notify = svn_wc_create_notify(conflict->path, action, pool); + notify->kind = conflict->node_kind; + notify->content_state = svn_wc_notify_state_conflicted; /* ### tree + conflict state */ + if (eb->notify_func) + (*eb->notify_func)(eb->notify_baton, notify, pool); +} + /* Delete PATH from its immediate parent PARENT_PATH, in the edit * represented by EB. Name temporary transactional logs based on * *LOG_NUMBER, but set *LOG_NUMBER to 0 after running the final log. @@ -1338,15 +1377,20 @@ do_entry_deletion(struct edit_baton *eb, const svn_wc_entry_t *entry; const char *full_path = svn_path_join(eb->anchor, path, pool); svn_stringbuf_t *log_item = svn_stringbuf_create("", pool); + svn_wc_conflict_description_t *tree_conflict; SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access, parent_path, pool)); SVN_ERR(svn_wc__entry_versioned(&entry, full_path, adm_access, FALSE, pool)); - SVN_ERR(check_tree_conflict(NULL, eb, log_item, full_path, entry, adm_access, + /* If the deletion will cause a tree conflict, record it. Allow the + * deletion to proceed, leaving any modified working item on disk. */ + SVN_ERR(check_tree_conflict(&tree_conflict, eb, log_item, full_path, + entry, adm_access, svn_wc_conflict_action_delete, pool)); + /* Delete the entry, and delete the working file unless it is modified. */ SVN_ERR(svn_wc__loggy_delete_entry(&log_item, adm_access, full_path, pool)); @@ -1419,7 +1463,10 @@ do_entry_deletion(struct edit_baton *eb, SVN_ERR(svn_wc__run_log(adm_access, NULL, pool)); *log_number = 0; - if (eb->notify_func) + /* Send a notification of what happened */ + if (tree_conflict) + notify_new_tree_conflict(eb, tree_conflict, pool); + else if (eb->notify_func) (*eb->notify_func) (eb->notify_baton, svn_wc_create_notify(full_path, @@ -1438,6 +1485,9 @@ delete_entry(const char *path, { struct dir_baton *pb = parent_baton; + if (pb->edit_baton->inside_a_tree_conflict) + return SVN_NO_ERROR; + SVN_ERR(check_path_under_root(pb->path, svn_path_basename(path, pool), pool)); return do_entry_deletion(pb->edit_baton, pb->path, path, &pb->log_number, @@ -1491,6 +1541,10 @@ add_directory(const char *path, || (!copyfrom_path && !SVN_IS_VALID_REVNUM(copyfrom_revision))); + /* Skip adding anything inside a deep conflict */ + if (pb->edit_baton->inside_a_tree_conflict) + return SVN_NO_ERROR; + SVN_ERR(check_path_under_root(pb->path, db->name, pool)); SVN_ERR(svn_io_check_path(db->path, &kind, db->pool)); @@ -1561,6 +1615,8 @@ add_directory(const char *path, { svn_wc_adm_access_t *parent_adm_access; const char *repos; + svn_wc_conflict_description_t *tree_conflict; + /* Use the repository root of the anchor, but only if it actually is an ancestor of the URL of this directory. */ if (eb->repos && svn_path_is_ancestor(eb->repos, db->new_URL)) @@ -1579,9 +1635,15 @@ add_directory(const char *path, pb->path, pool)); /* Raise a tree conflict if this directory is already present. */ - SVN_ERR(check_tree_conflict(NULL, eb, pb->log_accum, db->path, - entry, parent_adm_access, - svn_wc_conflict_action_add, pool)); + SVN_ERR(check_tree_conflict(&tree_conflict, eb, pb->log_accum, + db->path, entry, parent_adm_access, + svn_wc_conflict_action_add, + pool)); + if (tree_conflict) + { + notify_new_tree_conflict(eb, tree_conflict, pool); + return SVN_NO_ERROR; + } /* return svn_error_createf @@ -1720,6 +1782,46 @@ add_directory(const char *path, } +/* Set *{TEXT,PROP,TREE}_CONFLICTED_P to indicate whether the item at PATH + * with adm access ADM_ACCESS was in a state of text, property and tree + * conflict respectively. Each output parameter may be null if that output + * is not required. + */ +static svn_error_t * +conflicted_p3(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_error_t *err; + const svn_wc_entry_t *entry; + svn_boolean_t text_conflicted = FALSE; + svn_boolean_t prop_conflicted = FALSE; + svn_wc_conflict_description_t *tree_conflict; + + err = svn_wc_entry(&entry, path, adm_access, TRUE, pool); + if (err) + { + svn_error_clear(err); + entry = NULL; + } + + if (entry) + SVN_ERR(svn_wc_conflicted_p(&text_conflicted, &prop_conflicted, + path, entry, pool)); + SVN_ERR(svn_wc_get_tree_conflict(&tree_conflict, path, adm_access, pool)); + + if (text_conflicted_p) + *text_conflicted_p = text_conflicted; + if (prop_conflicted_p) + *prop_conflicted_p = prop_conflicted; + if (tree_conflicted_p) + *tree_conflicted_p = (tree_conflict != NULL); + return SVN_NO_ERROR; +} + /* An svn_delta_editor_t function. */ static svn_error_t * open_directory(const char *path, @@ -1759,19 +1861,15 @@ open_directory(const char *path, } /* Skip this directory if it already had (property or tree) conflicts. */ - if (entry) { - /* Text conflicts can't happen for a directory, but we need to supply - all flags. */ - svn_boolean_t text_conflicted; svn_boolean_t prop_conflicted; - svn_boolean_t has_tree_conflicted_children; + svn_boolean_t tree_conflicted; - SVN_ERR(svn_wc_conflicted_p2(&text_conflicted, &prop_conflicted, - &has_tree_conflicted_children, db->path, - entry, pool)); - SVN_ERR_ASSERT(! text_conflicted); - if (prop_conflicted || has_tree_conflicted_children) + /* Did this directory already have (property or tree) conflicts? */ + SVN_ERR(conflicted_p3(NULL, &prop_conflicted, &tree_conflicted, + db->path, adm_access, pool)); + + if (prop_conflicted || tree_conflicted) { db->bump_info->skipped = TRUE; apr_hash_set(eb->skipped_paths, apr_pstrdup(eb->pool, db->path), @@ -1783,18 +1881,34 @@ open_directory(const char *path, notify->kind = svn_node_dir; if (prop_conflicted) notify->prop_state = svn_wc_notify_state_conflicted; - /* ### if (tree_conflicted) - notify->tree_conflict_state = svn_wc_notify_state_conflicted; */ + if (tree_conflicted) + notify->content_state = svn_wc_notify_state_conflicted; /* ### */ (*eb->notify_func)(eb->notify_baton, notify, pool); } return SVN_NO_ERROR; } } - /* Raise a tree conflict if scheduled for deletion or similar. */ - SVN_ERR(check_tree_conflict(NULL, eb, pb->log_accum, db->path, entry, - parent_adm_access, svn_wc_conflict_action_edit, - pool)); + /* Raise a tree conflict and skip if scheduled for deletion or similar. */ + { + svn_wc_conflict_description_t *tree_conflict; + + /* Will this action cause a tree conflict? */ + SVN_ERR(check_tree_conflict(&tree_conflict, eb, pb->log_accum, db->path, + entry, parent_adm_access, + svn_wc_conflict_action_edit, pool)); + if (tree_conflict) + { + db->tree_conflict = tree_conflict; + eb->inside_a_tree_conflict++ /*= TRUE*/; + printf("## open_directory(%s): now %d TCs\n", path, eb->inside_a_tree_conflict); + + db->bump_info->skipped = TRUE; + apr_hash_set(eb->skipped_paths, apr_pstrdup(eb->pool, db->path), + APR_HASH_KEY_STRING, (void*)1); + } + return SVN_NO_ERROR; + } /* Mark directory as being at target_revision and URL, but incomplete. */ tmp_entry.revision = *(eb->target_revision); @@ -1827,7 +1941,7 @@ change_dir_prop(void *dir_baton, svn_prop_t *propchange; struct dir_baton *db = dir_baton; - if (db->bump_info->skipped) + if (db->bump_info->skipped || db->edit_baton->inside_a_tree_conflict) return SVN_NO_ERROR; propchange = apr_array_push(db->propchanges); @@ -1893,9 +2007,6 @@ close_directory(void *dir_baton, apr_array_header_t *entry_props, *wc_props, *regular_props; apr_hash_t *base_props = NULL, *working_props = NULL; svn_wc_adm_access_t *adm_access; - const svn_wc_entry_t *entry; - svn_boolean_t has_tree_conflicted_children; - svn_boolean_t text_conflicted, prop_conflicted; /* Dummies (never read). */ SVN_ERR(svn_categorize_props(db->propchanges, &entry_props, &wc_props, ®ular_props, pool)); @@ -2026,18 +2137,13 @@ close_directory(void *dir_baton, maybe_bump_dir_info() for more information. */ SVN_ERR(maybe_bump_dir_info(db->edit_baton, db->bump_info, db->pool)); - /* Check for tree conflicts in this directory. */ - SVN_ERR(svn_wc_entry(&entry, db->path, adm_access, TRUE, db->pool)); - SVN_ERR(svn_wc_conflicted_p2(&text_conflicted, &prop_conflicted, - &has_tree_conflicted_children, db->path, entry, - db->pool)); - - /* Notify of any prop changes on this directory -- but do nothing - if it's an added or skipped directory, because notification has already - happened in that case - unless the add was obstructed by a dir - scheduled for addition without history, in which case we handle - notification here). */ - if (! db->bump_info->skipped && (db->add_existed || (! db->added)) + /* Notify of any prop changes or tree conflict on this directory -- but do + * nothing if it's an added or skipped directory, because notification has + * already happened in that case - unless the add was obstructed by a dir + * scheduled for addition without history, in which case we handle + * notification here). */ + if (! (db->bump_info->skipped || db->edit_baton->inside_a_tree_conflict) + && (db->add_existed || (! db->added)) && (db->edit_baton->notify_func)) { svn_wc_notify_t *notify @@ -2048,11 +2154,17 @@ close_directory(void *dir_baton, pool); notify->kind = svn_node_dir; notify->prop_state = prop_state; - notify->content_state = has_tree_conflicted_children - ? svn_wc_notify_state_conflicted - : svn_wc_notify_state_unknown; - (*db->edit_baton->notify_func)(db->edit_baton->notify_baton, - notify, pool); + (*db->edit_baton->notify_func)(db->edit_baton->notify_baton, + notify, pool); + } + + /* If we have been skipping sub-tree editing because this directory is a + tree conflict victim, notify and then resume normal editing. */ + if (db->tree_conflict) + { + SVN_ERR_ASSERT(db->edit_baton->inside_a_tree_conflict); + db->edit_baton->inside_a_tree_conflict-- /* = FALSE */; + notify_new_tree_conflict(db->edit_baton, db->tree_conflict, pool); } return SVN_NO_ERROR; @@ -2505,6 +2617,7 @@ add_file(const char *path, svn_node_kind_t kind; svn_wc_adm_access_t *adm_access; apr_pool_t *subpool; + svn_wc_conflict_description_t *tree_conflict; if (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev)) { @@ -2535,8 +2648,13 @@ add_file(const char *path, /* Sanity checks. */ /* Raise a tree conflict if there's already something versioned here. */ - SVN_ERR(check_tree_conflict(NULL, eb, pb->log_accum, path, entry, adm_access, - svn_wc_conflict_action_add, pool)); + SVN_ERR(check_tree_conflict(&tree_conflict, eb, pb->log_accum, path, entry, + adm_access, svn_wc_conflict_action_add, pool)); + if (tree_conflict) + { + notify_new_tree_conflict(eb, tree_conflict, pool); + return SVN_NO_ERROR; + } /* When adding, there should be nothing with this name unless unversioned obstructions are permitted or the obstruction is scheduled for addition @@ -2625,7 +2743,8 @@ open_file(const char *path, svn_wc_adm_access_t *adm_access; svn_boolean_t text_conflicted; svn_boolean_t prop_conflicted; - svn_boolean_t has_tree_conflicted_children; + svn_boolean_t tree_conflicted; + svn_wc_conflict_description_t *tree_conflict; /* the file_pool can stick around for a *long* time, so we want to use a subpool for any temporary allocations. */ @@ -2652,22 +2771,15 @@ open_file(const char *path, fb->name, svn_path_local_style(pb->path, pool)); - /* If the file is scheduled for deletion, we have a tree conflict. - * This is use case 1 described in the paper attached to issue #2282 - * See also notes/tree-conflicts/detection.txt - */ - SVN_ERR(check_tree_conflict(NULL, eb, pb->log_accum, fb->path, entry, - adm_access, svn_wc_conflict_action_edit, pool)); - /* It is interesting to note: everything below is just validation. We aren't actually doing any "work" or fetching any persistent data. */ + /* Was the file already in conflict? */ + SVN_ERR(conflicted_p3(&text_conflicted, &prop_conflicted, + &tree_conflicted, fb->path, adm_access, pool)); + /* If the file was already in conflict, don't mess with it. */ - SVN_ERR(svn_wc_conflicted_p2(&text_conflicted, &prop_conflicted, - &has_tree_conflicted_children, pb->path, entry, - pool)); - SVN_ERR_ASSERT(! has_tree_conflicted_children); - if (text_conflicted || prop_conflicted) + if (text_conflicted || prop_conflicted || tree_conflicted) { fb->skipped = TRUE; apr_hash_set(eb->skipped_paths, apr_pstrdup(eb->pool, fb->path), @@ -2677,7 +2789,7 @@ open_file(const char *path, svn_wc_notify_t *notify = svn_wc_create_notify(fb->path, svn_wc_notify_skip, pool); notify->kind = svn_node_file; - notify->content_state = text_conflicted + notify->content_state = (text_conflicted || tree_conflicted) /* ### */ ? svn_wc_notify_state_conflicted : svn_wc_notify_state_unknown; notify->prop_state = prop_conflicted @@ -2687,6 +2799,23 @@ open_file(const char *path, } } + /* If the file is scheduled for deletion, we have a tree conflict. + * This is use case 1 described in the paper attached to issue #2282 + * See also notes/tree-conflicts/detection.txt + */ + SVN_ERR(check_tree_conflict(&tree_conflict, eb, pb->log_accum, fb->path, + entry, adm_access, svn_wc_conflict_action_edit, + pool)); + + /* If the file is now in conflict, don't mess with it. */ + if (tree_conflict) + { + fb->skipped = TRUE; + apr_hash_set(eb->skipped_paths, apr_pstrdup(eb->pool, fb->path), + APR_HASH_KEY_STRING, (void*)1); + notify_new_tree_conflict(eb, tree_conflict, pool); + } + svn_pool_destroy(subpool); return SVN_NO_ERROR; @@ -2764,7 +2893,7 @@ apply_textdelta(void *file_baton, svn_stream_t *source; svn_stream_t *target; - if (fb->skipped) + if (fb->skipped || fb->edit_baton->inside_a_tree_conflict) { *handler = svn_delta_noop_window_handler; *handler_baton = NULL; @@ -2900,7 +3029,7 @@ change_file_prop(void *file_baton, struct edit_baton *eb = fb->edit_baton; svn_prop_t *propchange; - if (fb->skipped) + if (fb->skipped || fb->edit_baton->inside_a_tree_conflict) return SVN_NO_ERROR; /* Push a new propchange to the file baton's array of propchanges */ @@ -3437,7 +3566,7 @@ close_file(void *file_baton, svn_checksum_t *expected_checksum = NULL; svn_checksum_t *actual_checksum; - if (fb->skipped) + if (fb->skipped || fb->edit_baton->inside_a_tree_conflict) return maybe_bump_dir_info(eb, fb->bump_info, pool); if (expected_hex_digest) Index: subversion/tests/cmdline/commit_tests.py =================================================================== --- subversion/tests/cmdline/commit_tests.py (revision 33895) +++ subversion/tests/cmdline/commit_tests.py (working copy) @@ -2644,28 +2644,17 @@ def tree_conflicts_resolved(sbox): svntest.actions.build_greek_tree_conflicts(sbox) wc_dir = sbox.wc_dir - # Duplicate wc for tests - wc_dir_2 = sbox.add_wc_path('2') - svntest.actions.duplicate_dir(wc_dir, wc_dir_2) - - # Resolved in directory containing tree conflicts + # Mark the parent directory as resolved (### will be redundant) G = os.path.join(wc_dir, 'A', 'D', 'G') svntest.actions.run_and_verify_svn(None, None, [], 'resolved', G) + # Mark the conflict victims as resolved (### not yet implemented) + #pi = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') + #rho = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + #tau = os.path.join(wc_dir, 'A', 'D', 'G', 'tau') + #svntest.actions.run_and_verify_svn(None, None, [], 'resolved', pi, rho, tau) - expected_status = svntest.actions.get_virginal_state(wc_dir, 2) - expected_status.tweak('A/D/G/pi', status='D ') - expected_status.remove('A/D/G/rho', - 'A/D/G/tau') - - svntest.actions.run_and_verify_status(wc_dir, expected_status) - - # Recursively resolved in parent directory -- expect same result - D2 = os.path.join(wc_dir_2, 'A', 'D') - G2 = os.path.join(wc_dir_2, 'A', 'D', 'G') - svntest.actions.run_and_verify_svn(None, None, [], 'resolved', D2, '-R') - - expected_status.wc_dir = wc_dir_2 - svntest.actions.run_and_verify_status(wc_dir_2, expected_status) + # Now try to commit: it shoud succeed + svntest.actions.run_and_verify_commit(wc_dir, None, None, None, G) ######################################################################## Index: subversion/tests/cmdline/svntest/actions.py =================================================================== --- subversion/tests/cmdline/svntest/actions.py (revision 33895) +++ subversion/tests/cmdline/svntest/actions.py (working copy) @@ -1733,16 +1733,19 @@ def deep_trees_leaf_del(base): deep_trees_after_leaf_del = wc.State('', { 'F' : Item(), 'D' : Item(), + 'D/D1' : Item(), 'DF' : Item(), 'DF/D1' : Item(), 'DD' : Item(), 'DD/D1' : Item(), + 'DD/D1/D2' : Item(), 'DDF' : Item(), 'DDF/D1' : Item(), 'DDF/D1/D2' : Item(), 'DDD' : Item(), 'DDD/D1' : Item(), 'DDD/D1/D2' : Item(), + 'DDD/D1/D2/D3' : Item(), }) @@ -1761,10 +1764,19 @@ def deep_trees_tree_del(base): deep_trees_after_tree_del = wc.State('', { 'F' : Item(), 'D' : Item(), + 'D/D1' : Item(), 'DF' : Item(), + 'DF/D1' : Item(), 'DD' : Item(), + 'DD/D1' : Item(), + 'DD/D1/D2' : Item(), 'DDF' : Item(), + 'DDF/D1' : Item(), + 'DDF/D1/D2' : Item(), 'DDD' : Item(), + 'DDD/D1' : Item(), + 'DDD/D1/D2' : Item(), + 'DDD/D1/D2/D3' : Item(), }) # Expected merge/update/switch output if tree conflict detection really works. Index: subversion/tests/cmdline/switch_tests.py =================================================================== --- subversion/tests/cmdline/switch_tests.py (revision 33895) +++ subversion/tests/cmdline/switch_tests.py (working copy) @@ -2172,53 +2172,41 @@ def tree_conflicts_on_switch_1_1(sbox): # ### TODO: Skip the deletion of tree conflict victims. expected_output = svntest.wc.State('', { 'D' : Item(status='C '), - 'D/D1' : Item(), - 'D/D1/delta' : Item(status='A '), 'F' : Item(status='C '), - 'F/alpha' : Item(status='U '), + 'F/alpha' : Item(verb='Skipped'), 'DD' : Item(status='C '), - 'DD/D1' : Item(status='C '), - 'DD/D1/D2' : Item(), - 'DD/D1/D2/epsilon' : Item(status='A '), 'DF' : Item(status='C '), - 'DF/D1' : Item(status='C '), - 'DF/D1/beta' : Item(status='U '), + 'DF/D1/beta' : Item(verb='Skipped'), 'DDD' : Item(status='C '), - 'DDD/D1' : Item(status='C '), - 'DDD/D1/D2' : Item(status='C '), - 'DDD/D1/D2/D3' : Item(), - 'DDD/D1/D2/D3/zeta' : Item(status='A '), 'DDF' : Item(status='C '), - 'DDF/D1' : Item(status='C '), - 'DDF/D1/D2' : Item(status='C '), - 'DDF/D1/D2/gamma' : Item(status='U '), + 'DDF/D1/D2/gamma' : Item(verb='Skipped'), }) - expected_disk = state_after_leaf_edit + expected_skip = svntest.wc.State('', { + }) + + expected_disk = state_after_tree_del expected_status = svntest.wc.State('', { '' : Item(status=' ', wc_rev='3'), - 'D' : Item(status=' ', wc_rev='3'), + 'D' : Item(status='C ', wc_rev='3', treeconflict=' '), 'D/D1' : Item(status='D ', wc_rev='3', treeconflict='C'), - 'D/D1/delta' : Item(status=' ', wc_rev='3'), - 'F' : Item(status=' ', wc_rev='3'), - 'F/alpha' : Item(status='D ', wc_rev='3', treeconflict='C'), - 'DD' : Item(status=' ', wc_rev='3'), + 'DD' : Item(status='C ', wc_rev='3', treeconflict=' '), + 'F' : Item(status='C ', wc_rev='3', treeconflict=' '), + 'F/alpha' : Item(status='D ', wc_rev='2', treeconflict='C', switched='S'), 'DD/D1' : Item(status='D ', wc_rev='3', treeconflict='C'), 'DD/D1/D2' : Item(status='D ', wc_rev='3', treeconflict='C'), - 'DD/D1/D2/epsilon' : Item(status=' ', wc_rev='3'), - 'DF' : Item(status=' ', wc_rev='3'), + 'DDD' : Item(status='C ', wc_rev='3', treeconflict=' '), + 'DF' : Item(status='C ', wc_rev='3', treeconflict=' '), 'DF/D1' : Item(status='D ', wc_rev='3', treeconflict='C'), - 'DF/D1/beta' : Item(status='D ', wc_rev='3', treeconflict='C'), - 'DDD' : Item(status=' ', wc_rev='3'), + 'DF/D1/beta' : Item(status='D ', wc_rev='2', treeconflict='C', switched='S'), 'DDD/D1' : Item(status='D ', wc_rev='3', treeconflict='C'), 'DDD/D1/D2' : Item(status='D ', wc_rev='3', treeconflict='C'), 'DDD/D1/D2/D3' : Item(status='D ', wc_rev='3', treeconflict='C'), - 'DDD/D1/D2/D3/zeta' : Item(status=' ', wc_rev='3'), - 'DDF' : Item(status=' ', wc_rev='3'), + 'DDF' : Item(status='C ', wc_rev='3', treeconflict=' '), 'DDF/D1' : Item(status='D ', wc_rev='3', treeconflict='C'), 'DDF/D1/D2' : Item(status='D ', wc_rev='3', treeconflict='C'), - 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev='3', treeconflict='C'), + 'DDF/D1/D2/gamma' : Item(status='D ', wc_rev='2', treeconflict='C', switched='S'), }) svntest.actions.deep_trees_run_tests_scheme_for_switch(sbox, @@ -2227,7 +2215,8 @@ def tree_conflicts_on_switch_1_1(sbox): leaf_edit, expected_output, expected_disk, - expected_status) ] ) + expected_status, + expected_skip) ] ) def tree_conflicts_on_switch_1_2(sbox): @@ -2237,23 +2226,13 @@ def tree_conflicts_on_switch_1_2(sbox): expected_output = svntest.wc.State('', { 'D' : Item(status='C '), - 'D/D1' : Item(status='D '), + 'D/D1' : Item(status='D '), ### should be skipped! 'F' : Item(status='C '), - 'F/alpha' : Item(status='D '), + 'F/alpha' : Item(status='D '), ### should be skipped! 'DD' : Item(status='C '), - 'DD/D1' : Item(status='C '), - 'DD/D1/D2' : Item(status='D '), 'DF' : Item(status='C '), - 'DF/D1' : Item(status='C '), - 'DF/D1/beta' : Item(status='D '), 'DDD' : Item(status='C '), - 'DDD/D1' : Item(status='C '), - 'DDD/D1/D2' : Item(status='C '), - 'DDD/D1/D2/D3' : Item(status='D '), 'DDF' : Item(status='C '), - 'DDF/D1' : Item(status='C '), - 'DDF/D1/D2' : Item(status='C '), - 'DDF/D1/D2/gamma' : Item(status='D '), }) expected_disk = state_after_leaf_del @@ -2442,7 +2421,7 @@ def tree_conflicts_on_switch_2_2(sbox): 'DDF/D1' : Item(status='D '), }) - expected_disk = state_after_tree_del + expected_disk = state_after_leaf_del expected_status = svntest.wc.State('', { '' : Item(status=' ', wc_rev='3'), Index: subversion/tests/cmdline/update_tests.py =================================================================== --- subversion/tests/cmdline/update_tests.py (revision 33895) +++ subversion/tests/cmdline/update_tests.py (working copy) @@ -4258,6 +4258,51 @@ def tree_conflicts_on_update_3(sbox): expected_status) +def tree_conflicts_sandbox(sbox): + "set up tree-conflict scenarios for manual testing" + sbox.build() + os.chdir(sbox.wc_dir) + + j = os.path.join + + def do_edit(file): + svntest.main.file_append(file, "Edited text.\n") + + def do_move(src_file, dst_file): + # Actually, let's not try "move" yet; it's more complicated. Let's just try "delete" first. + svntest.main.run_svn(None, 'move', src_file, dst_file) + #svntest.main.run_svn(None, 'delete', src_file) + + # Make three identical subdirectories corresponding to use cases 1-3 in + # notes/tree-conflicts/use-cases.txt + + # Commit the initial state: a file 'Foo.c' in each. (r2) + for dir in ['uc1', 'uc2', 'uc3']: + os.mkdir(dir) + alpha = j(dir, 'Foo.c') + svntest.main.file_append(alpha, "This is the file 'Foo.c'.\n") + svntest.main.run_svn(None, 'add', dir) + svntest.main.run_svn(None, 'commit', '-m', 'initial state') + + # Commit some changes in each subdirectory (r3) + do_edit(j('uc1', 'Foo.c')) + do_move(j('uc2', 'Foo.c'), j('uc2', 'Bar.c')) + do_move(j('uc3', 'Foo.c'), j('uc3', 'Bar.c')) + svntest.main.run_svn(None, 'commit', '-m', 'incoming changes') + + # Return to the WC to its initial state (r2) + svntest.main.run_svn(None, 'update', '-r2') + + # Make some local, uncommitted changes in each subdirectory + do_move(j('uc1', 'Foo.c'), j('uc1', 'Bar.c')) + do_edit(j('uc2', 'Foo.c')) + do_move(j('uc3', 'Foo.c'), j('uc3', 'Bix.c')) + + # Now this pseudo-test has done the set-up work, the user can go into one + # of the subdirectories and try out some operations such as "update", + # "resolve", "commit", "status", etc. + + ####################################################################### # Run the tests @@ -4317,6 +4362,7 @@ test_list = [ None, tree_conflicts_on_update_2_1, tree_conflicts_on_update_2_2, tree_conflicts_on_update_3, + tree_conflicts_sandbox, ] if __name__ == '__main__':