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. (check_tree_conflict): Add an output parameter to return the conflict raised. (do_entry_deletion): Adjust calls to check_tree_conflict() accordingly. (delete_entry): Skip if inside a conflict. (add_directory): Skip if inside a conflict. Tweak calls to check_tree_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. (add_file): Tweak the call to check_tree_conflict(). (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/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 33877) +++ 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,10 @@ struct dir_baton (In other words, are we resuming an interrupted update?) */ svn_boolean_t was_incomplete; + /* Is this directory a tree conflict victim? (If so, we will want to + avoid actually changing anything until we close this directory.) */ + svn_boolean_t is_a_tree_conflict_victim; + /* The pool in which this baton itself is allocated. */ apr_pool_t *pool; }; @@ -583,7 +591,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)); } @@ -1199,9 +1207,13 @@ tree_has_local_mods(svn_boolean_t *modif * schedule-delete etc.), or NULL if FULL_PATH is unversioned. * PARENT_ADM_ACCESS is the admin access baton of FULL_PATH's parent * directory. + * + * If PCONFLICT is not null, set *PCONFLICT to the conflict description if + * there is one or else to null. */ static svn_error_t * -check_tree_conflict(struct edit_baton *eb, +check_tree_conflict(svn_wc_conflict_description_t **pconflict, + struct edit_baton *eb, svn_stringbuf_t *log_accum, const char *full_path, const svn_wc_entry_t *entry, @@ -1291,6 +1303,9 @@ check_tree_conflict(struct edit_baton *e break; } + if (pconflict) + *pconflict = NULL; + /* If a conflict was detected, append log commands to the log accumulator * to record it. */ if (reason != (svn_wc_conflict_reason_t)(-1)) @@ -1307,6 +1322,9 @@ check_tree_conflict(struct edit_baton *e SVN_ERR(svn_wc__loggy_add_tree_conflict_data(log_accum, conflict, parent_adm_access, pool)); + + if (pconflict) + *pconflict = conflict; } return SVN_NO_ERROR; @@ -1334,7 +1352,7 @@ do_entry_deletion(struct edit_baton *eb, SVN_ERR(svn_wc__entry_versioned(&entry, full_path, adm_access, FALSE, pool)); - SVN_ERR(check_tree_conflict(eb, log_item, full_path, entry, adm_access, + SVN_ERR(check_tree_conflict(NULL, eb, log_item, full_path, entry, adm_access, svn_wc_conflict_action_delete, pool)); SVN_ERR(svn_wc__loggy_delete_entry(&log_item, adm_access, full_path, @@ -1428,6 +1446,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, @@ -1481,6 +1502,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)); @@ -1569,8 +1594,8 @@ add_directory(const char *path, pb->path, pool)); /* Raise a tree conflict if this directory is already present. */ - SVN_ERR(check_tree_conflict(eb, pb->log_accum, db->path, entry, - parent_adm_access, + SVN_ERR(check_tree_conflict(NULL, eb, pb->log_accum, db->path, + entry, parent_adm_access, svn_wc_conflict_action_add, pool)); /* @@ -1710,6 +1735,48 @@ 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_boolean_t has_tree_conflicted_children = 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_p2(&text_conflicted, &prop_conflicted, + &has_tree_conflicted_children, 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, @@ -1749,19 +1816,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; + + /* Did this directory already have (property or tree) conflicts? */ + SVN_ERR(conflicted_p3(NULL, &prop_conflicted, &tree_conflicted, + db->path, adm_access, pool)); - 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) + if (prop_conflicted || tree_conflicted) { db->bump_info->skipped = TRUE; apr_hash_set(eb->skipped_paths, apr_pstrdup(eb->pool, db->path), @@ -1773,18 +1836,42 @@ 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(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) + { + eb->inside_a_tree_conflict++ /*= TRUE*/; + db->is_a_tree_conflict_victim = 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); + if (eb->notify_func) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(db->path, svn_wc_notify_skip, pool); + notify->kind = svn_node_dir; + notify->content_state = svn_wc_notify_state_conflicted; /* ### */ + (*eb->notify_func)(eb->notify_baton, notify, pool); + } + } + return SVN_NO_ERROR; + } /* Mark directory as being at target_revision and URL, but incomplete. */ tmp_entry.revision = *(eb->target_revision); @@ -1817,7 +1904,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); @@ -1883,9 +1970,7 @@ 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_boolean_t tree_conflicted; SVN_ERR(svn_categorize_props(db->propchanges, &entry_props, &wc_props, ®ular_props, pool)); @@ -2017,17 +2102,16 @@ close_directory(void *dir_baton, 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)) + SVN_ERR(conflicted_p3(NULL, NULL, &tree_conflicted, db->path, adm_access, + db->pool)); + + /* 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 @@ -2038,13 +2122,22 @@ close_directory(void *dir_baton, pool); notify->kind = svn_node_dir; notify->prop_state = prop_state; - notify->content_state = has_tree_conflicted_children + notify->content_state = tree_conflicted ? svn_wc_notify_state_conflicted : svn_wc_notify_state_unknown; (*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, go back to normal editing. */ + if (db->is_a_tree_conflict_victim) + { + SVN_ERR_ASSERT(db->edit_baton->inside_a_tree_conflict); + db->edit_baton->inside_a_tree_conflict-- /* = FALSE */; + db->is_a_tree_conflict_victim = FALSE; + } + return SVN_NO_ERROR; } @@ -2525,7 +2618,7 @@ add_file(const char *path, /* Sanity checks. */ /* Raise a tree conflict if there's already something versioned here. */ - SVN_ERR(check_tree_conflict(eb, pb->log_accum, path, entry, adm_access, + SVN_ERR(check_tree_conflict(NULL, eb, pb->log_accum, path, entry, adm_access, svn_wc_conflict_action_add, pool)); /* When adding, there should be nothing with this name unless unversioned @@ -2615,7 +2708,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. */ @@ -2642,22 +2736,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(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), @@ -2667,7 +2754,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 @@ -2677,6 +2764,30 @@ 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); + if (eb->notify_func) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(fb->path, svn_wc_notify_skip, pool); + notify->kind = svn_node_file; + notify->content_state = svn_wc_notify_state_conflicted; + (*eb->notify_func)(eb->notify_baton, notify, pool); + } + } + svn_pool_destroy(subpool); return SVN_NO_ERROR; @@ -2754,7 +2865,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; @@ -2890,7 +3001,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 */ @@ -3427,7 +3538,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/svntest/actions.py =================================================================== --- subversion/tests/cmdline/svntest/actions.py (revision 33877) +++ 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 33877) +++ 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 33877) +++ 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__':