Index: subversion/libsvn_wc/update_editor.c =================================================================== --- subversion/libsvn_wc/update_editor.c (revision 21505) +++ subversion/libsvn_wc/update_editor.c (working copy) @@ -139,6 +139,14 @@ yet in the parent's list of entries */ svn_boolean_t added; + /* Set if an unversioned dir of the same name already existed in + this directory. */ + svn_boolean_t existed; + + /* Set if a dir of the same name already exists and is + scheduled for addition without history. */ + svn_boolean_t add_existed; + /* An array of svn_prop_t structures, representing all the property changes to be applied to this directory. */ apr_array_header_t *propchanges; @@ -342,6 +350,8 @@ d->pool = svn_pool_create(pool); d->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t)); d->added = added; + d->existed = FALSE; + d->add_existed = FALSE; d->bump_info = bdi; d->log_number = 0; @@ -522,6 +532,10 @@ this directory. */ svn_boolean_t existed; + /* Set if a file of the same name already exists and is + scheduled for addition without history. */ + svn_boolean_t add_existed; + /* This gets set if the file underwent a text change, which guides the code that syncs up the adm dir and working copy. */ svn_boolean_t text_changed; @@ -585,6 +599,7 @@ f->bump_info = pb->bump_info; f->added = adding; f->existed = FALSE; + f->add_existed = FALSE; f->dir_baton = pb; /* No need to initialize f->digest, since we used pcalloc(). */ @@ -687,10 +702,22 @@ = db->edit_baton->adm_access ? svn_wc_adm_access_pool(db->edit_baton->adm_access) : db->edit_baton->pool; + svn_error_t *err = svn_wc_adm_open3(&adm_access, + db->edit_baton->adm_access, + db->path, TRUE, 0, NULL, NULL, + adm_access_pool); - SVN_ERR(svn_wc_adm_open3(&adm_access, db->edit_baton->adm_access, - db->path, TRUE, 0, NULL, NULL, - adm_access_pool)); + /* db->path may be scheduled for addition without history. + In that case db->edit_baton->adm_access already has it locked. */ + if (err && err->apr_err == SVN_ERR_WC_LOCKED) + { + err = svn_wc_adm_retrieve(&adm_access, + db->edit_baton->adm_access, + db->path, adm_access_pool); + } + + SVN_ERR(err); + if (!db->edit_baton->adm_access) db->edit_baton->adm_access = adm_access; } @@ -1058,7 +1085,6 @@ struct edit_baton *eb = pb->edit_baton; struct dir_baton *db = make_dir_baton(path, eb, pb, TRUE, pool); svn_node_kind_t kind; - svn_boolean_t exists = FALSE; /* Semantic check. Either both "copyfrom" args are valid, or they're NULL and SVN_INVALID_REVNUM. A mixture is illegal semantics. */ @@ -1068,60 +1094,71 @@ SVN_ERR(svn_io_check_path(db->path, &kind, db->pool)); - /* Check for obstructions. */ - if (eb->allow_unver_obstructions) + /* The path can exist, but it must be a directory... */ + if (kind == svn_node_file || kind == svn_node_unknown) + return svn_error_createf + (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to add directory '%s': a non-directory object of the " + "same name already exists"), + svn_path_local_style(db->path, pool)); + + if (kind == svn_node_dir) { - /* The path can exist, but it must be a directory... */ - if (kind == svn_node_file || kind == svn_node_unknown) - return svn_error_createf - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Failed to add directory '%s': a non-directory object of the " - "same name already exists"), - svn_path_local_style(db->path, pool)); - if (kind == svn_node_dir) + /* ...Ok, it's a directory but it can't be versioned or + scheduled for addition with history. */ + svn_wc_adm_access_t *adm_access; + + /* Test the obstructing dir to see if it's versioned. */ + svn_error_t *err = svn_wc_adm_open3(&adm_access, NULL, + db->path, FALSE, 0, + NULL, NULL, pool); + + if (err && err->apr_err != SVN_ERR_WC_NOT_DIRECTORY) { - /* ...Ok, it's a directory but it can't be versioned. We don't - handle that, yet... */ - svn_wc_adm_access_t *adm_access; - - /* Test the obstructing dir to see if it's versioned. */ - svn_error_t *err = svn_wc_adm_open3(&adm_access, NULL, - db->path, FALSE, 0, - NULL, NULL, pool); - if (err && err->apr_err != SVN_ERR_WC_NOT_DIRECTORY) + /* Something quite unexepected has happened. */ + return err; + } + else if (err) /* Not a versioned dir. */ + { + if (eb->allow_unver_obstructions) { - /* Something quite unexepected has happened. */ - return err; - } - else if (err) - { /* Obstructing dir is not versioned, just need to flag it as existing then we are done here. */ - exists = TRUE; + db->existed = TRUE; svn_error_clear(err); } else { - /* Obstructing dir *is* versioned. */ return svn_error_createf (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Failed to forcibly add directory '%s': a versioned " + _("Failed to add directory '%s': an unversioned " "directory of the same name already exists"), svn_path_local_style(db->path, pool)); } } + else /* Obstructing dir *is* versioned or scheduled for addition. */ + { + const svn_wc_entry_t *entry; + SVN_ERR(svn_wc_entry(&entry, db->path, adm_access, FALSE, pool)); + + /* Anything other than a dir scheduled for addition without + history is an error. */ + if (entry + && entry->schedule == svn_wc_schedule_add + && ! entry->copied) + { + db->add_existed = TRUE; + } + else + { + return svn_error_createf + (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to add directory '%s': a versioned " + "directory of the same name already exists"), + svn_path_local_style(db->path, pool)); + } + } } - else - { - /* There should be nothing with this name if obstructions are - not allowed. */ - if (kind != svn_node_none) - return svn_error_createf - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Failed to add directory '%s': " - "object of the same name already exists"), - svn_path_local_style(db->path, pool)); - } /* It may not be named the same as the administrative directory. */ if (svn_wc_is_adm_dir(svn_path_basename(path, pool), pool)) @@ -1148,27 +1185,19 @@ else /* ...or we got invalid copyfrom args. */ { svn_wc_adm_access_t *adm_access; - const svn_wc_entry_t *dir_entry; - apr_hash_t *entries; svn_wc_entry_t tmp_entry; + apr_uint32_t modify_flags = SVN_WC__ENTRY_MODIFY_KIND | + SVN_WC__ENTRY_MODIFY_DELETED | SVN_WC__ENTRY_MODIFY_ABSENT; - /* Extra check: a directory by this name may not exist, but there - may still be one scheduled for addition. That's a genuine - tree-conflict. */ SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access, pb->path, db->pool)); - SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, db->pool)); - dir_entry = apr_hash_get(entries, db->name, APR_HASH_KEY_STRING); - if (dir_entry && dir_entry->schedule == svn_wc_schedule_add) - return svn_error_createf - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Failed to add directory '%s': object of the same name " - "is already scheduled for addition"), - svn_path_local_style(path, pool)); /* Immediately create an entry for the new directory in the parent. Note that the parent must already be either added or opened, and - thus it's in an 'incomplete' state just like the new dir. */ + thus it's in an 'incomplete' state just like the new dir. + The entry may already exist if the new directory is already + scheduled for addition without history, in that case set + its schedule to normal. */ tmp_entry.kind = svn_node_dir; /* Note that there may already exist a 'ghost' entry in the parent with the same name, in a 'deleted' or 'absent' state. @@ -1176,11 +1205,43 @@ we get rid of the state flag when doing so: */ tmp_entry.deleted = FALSE; tmp_entry.absent = FALSE; + + if (db->add_existed) + { + tmp_entry.schedule = svn_wc_schedule_normal; + modify_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE | + SVN_WC__ENTRY_MODIFY_FORCE; + } + SVN_ERR(svn_wc__entry_modify(adm_access, db->name, &tmp_entry, - (SVN_WC__ENTRY_MODIFY_KIND | - SVN_WC__ENTRY_MODIFY_DELETED | - SVN_WC__ENTRY_MODIFY_ABSENT), + modify_flags, TRUE /* immediate write */, pool)); + + if (db->add_existed) + { + /* Immediately tweak the schedule for "this dir" so it too + is no longer scheduled for addition. Change rev from 0 + to the target revision allowing prep_directory() to do + its thing without error. */ + modify_flags = SVN_WC__ENTRY_MODIFY_SCHEDULE + | SVN_WC__ENTRY_MODIFY_FORCE | SVN_WC__ENTRY_MODIFY_REVISION; + + SVN_ERR(svn_wc_adm_retrieve(&adm_access, + db->edit_baton->adm_access, + db->path, pool)); + tmp_entry.revision = *(eb->target_revision); + + if (eb->switch_url) + { + tmp_entry.url = svn_path_url_add_component(eb->switch_url, + db->name, pool); + modify_flags |= SVN_WC__ENTRY_MODIFY_URL; + } + + SVN_ERR(svn_wc__entry_modify(adm_access, NULL, &tmp_entry, + modify_flags, + TRUE /* immediate write */, pool)); + } } SVN_ERR(prep_directory(db, @@ -1190,11 +1251,15 @@ *child_baton = db; - if (eb->notify_func) + /* If this add was obstructed by dir scheduled for addition without + history let close_file() handle the notification because there + might be properties to deal with. */ + if (eb->notify_func && !(db->add_existed)) { svn_wc_notify_t *notify = svn_wc_create_notify( db->path, - exists ? svn_wc_notify_exists : svn_wc_notify_update_add, + db->existed ? + svn_wc_notify_exists : svn_wc_notify_update_add, pool); notify->kind = svn_node_dir; (*eb->notify_func)(eb->notify_baton, notify, pool); @@ -1296,10 +1361,9 @@ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; apr_array_header_t *entry_props, *wc_props, *regular_props; svn_wc_adm_access_t *adm_access; - SVN_ERR(svn_categorize_props(db->propchanges, &entry_props, &wc_props, ®ular_props, pool)); - + SVN_ERR(svn_wc_adm_retrieve(&adm_access, db->edit_baton->adm_access, db->path, db->pool)); @@ -1392,11 +1456,17 @@ /* Notify of any prop changes on this directory -- but do nothing if it's an added directory, because notification has already - happened in that case. */ - if ((! db->added) && (db->edit_baton->notify_func)) + 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->add_existed || (! db->added)) && (db->edit_baton->notify_func)) { svn_wc_notify_t *notify - = svn_wc_create_notify(db->path, svn_wc_notify_update_update, pool); + = svn_wc_create_notify(db->path, + db->existed || db->add_existed + ? svn_wc_notify_exists + : svn_wc_notify_update_update, + pool); notify->kind = svn_node_dir; notify->prop_state = prop_state; (*db->edit_baton->notify_func)(db->edit_baton->notify_baton, @@ -1519,15 +1589,27 @@ /* Sanity checks. */ - /* If adding, there should be nothing with this name unless obstructions - are permitted. */ + /* If adding, there should be nothing with this name unless unversioned + obstructions are permitted or the obstruction is scheduled for addition + without history. */ if (adding && (kind != svn_node_none)) { - if (eb->allow_unver_obstructions) + if (eb->allow_unver_obstructions + || (entry && entry->schedule == svn_wc_schedule_add)) { - /* The name can exist, but it better *really* be a file. If - it is treat the existing file as a WC modification by the - user. */ + if (entry && entry->copied) + { + return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, + NULL, + _("Failed to add file '%s': a " + "file of the same name is " + "already scheduled for addition " + "with history"), + svn_path_local_style(fb->path, + pool)); + } + + /* The name can exist, but it better *really* be a file. */ if (kind != svn_node_file) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, @@ -1536,7 +1618,11 @@ "name already exists"), svn_path_local_style(fb->path, pool)); - fb->existed = TRUE; + + if (entry) + fb->add_existed = TRUE; /* Flag as addition without history. */ + else + fb->existed = TRUE; /* Flag as unversioned obstruction. */ } else { @@ -1554,21 +1640,8 @@ It certainly doesn't hurt to re-add the file. We can't possibly get the entry showing up twice in `entries', since it's a hash; and we know that we won't lose any local mods. Let the existing - entry be overwritten. + entry be overwritten. */ - sussman follows up to himself, many months later: the above - scenario is fine, as long as the pre-existing entry isn't - scheduled for addition. that's a genuine tree-conflict, - regardless of whether the working file still exists. */ - - if (adding && entry && entry->schedule == svn_wc_schedule_add) - return svn_error_createf - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Failed to add file '%s': object of the same name is already " - "scheduled for addition"), - svn_path_local_style(fb->path, pool)); - - /* If replacing, make sure the .svn entry already exists. */ if ((! adding) && (! entry)) return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, @@ -1931,6 +2004,9 @@ * version control, and FILE_PATH differs from temporary text-base then * don't merge any textual changes, instead leave FILE_PATH as-is. * + * If ADD_EXISTED is TRUE, FILE_PATH exists and is scheduled for addition + * without history. + * * The caller also provides the property changes for the file in the * PROP_CHANGES array; if there are no prop changes, then the caller must pass * NULL instead. This argument is an array of svn_prop_t structures, @@ -1977,6 +2053,7 @@ svn_revnum_t new_revision, svn_boolean_t has_new_text_base, svn_boolean_t use_obstruction, + svn_boolean_t add_existed, const apr_array_header_t *prop_changes, const char *new_URL, const char *diff3_cmd, @@ -2048,12 +2125,37 @@ if (entry && entry->schedule == svn_wc_schedule_replace) is_replaced = TRUE; } + + if (add_existed) + { + /* Immediately tweak schedule for FILE_PATH's entry so it is no + longer scheduled for addition.*/ + svn_wc_entry_t tmp_entry; + tmp_entry.schedule = svn_wc_schedule_normal; + SVN_ERR(svn_wc__entry_modify(adm_access, + base_name, + &tmp_entry, + SVN_WC__ENTRY_MODIFY_SCHEDULE + | SVN_WC__ENTRY_MODIFY_FORCE, + TRUE, + pool)); + } if (new_text_path) /* is there a new text-base to install? */ { if (!is_replaced) { txtb = svn_wc__text_base_path(base_name, FALSE, pool); + + /* Files scheduled for addition without history don't have + a text-base so create an empty one. */ + if (add_existed) + { + const char *txtb_fullpath = svn_path_join( + svn_path_dirname(file_path, pool), txtb, pool); + SVN_ERR(svn_io_file_create(txtb_fullpath, "", pool)); + } + tmp_txtb = svn_wc__text_base_path(base_name, TRUE, pool); } else @@ -2097,18 +2199,18 @@ /* For 'textual' merging, we implement this matrix. - Text file Binary File - ----------------------------------------------- - Local Mods && | svn_wc_merge uses diff3, | svn_wc_merge | - !USE_OBSTRUCTION | possibly makes backups & | makes backups, | - | marks file as conflicted.| marks conflicted | - ----------------------------------------------- - "Local Mods" && | Just leave obstructing file as-is. | - USE_OBSTRUCTION | | - ----------------------------------------------- - No Mods | Just overwrite working file. | - | | - ----------------------------------------------- + Text file Binary File + ----------------------------------------------- + "Local Mods" && | svn_wc_merge uses diff3, | svn_wc_merge | + (!USE_OBSTRUCTION || | possibly makes backups & | makes backups, | + ADD_EXISTED) | marks file as conflicted.| marks conflicted | + ----------------------------------------------- + "Local Mods" && | Just leave obstructing file as-is. | + USE_OBSTRUCTION | | + ----------------------------------------------- + No Mods | Just overwrite working file. | + | | + ----------------------------------------------- So the first thing we do is figure out where we are in the matrix. */ @@ -2137,7 +2239,7 @@ svn_wc__copy_translate, tmp_txtb, base_name, FALSE, pool)); } - else if (use_obstruction) + else if (use_obstruction && ! add_existed) { /* A valid obstruction to an added file exists and it's text differs from the added file. */ @@ -2150,7 +2252,8 @@ &log_accum, adm_access, base_name, &tmp_entry, SVN_WC__ENTRY_MODIFY_TEXT_TIME, pool)); } - else /* working file exists, and has local mods.*/ + else /* working file exists and has local mods + or is scheduled for addition.*/ { /* Now we need to let loose svn_wc_merge2() to merge the textual changes into the working file. */ @@ -2319,6 +2422,7 @@ (*eb->target_revision), fb->text_changed, fb->existed, + fb->add_existed, propchanges, fb->new_URL, eb->diff3_cmd, @@ -2339,14 +2443,20 @@ (lock_state != svn_wc_notify_lock_state_unchanged)) && eb->notify_func) { - svn_wc_notify_t *notify - = svn_wc_create_notify(fb->path, - (fb->existed - ? svn_wc_notify_exists - : (fb->added - ? svn_wc_notify_update_add - : svn_wc_notify_update_update)), - pool); + svn_wc_notify_t *notify; + svn_wc_notify_action_t action = svn_wc_notify_update_update; + + if (fb->existed || fb->add_existed) + { + if (content_state != svn_wc_notify_state_conflicted) + action = svn_wc_notify_exists; + } + else if (fb->added) + { + action = svn_wc_notify_update_add; + } + + notify = svn_wc_create_notify(fb->path, action, pool); notify->kind = svn_node_file; notify->content_state = content_state; notify->prop_state = prop_state; Index: subversion/svn/notify.c =================================================================== --- subversion/svn/notify.c (revision 21505) +++ subversion/svn/notify.c (working copy) @@ -100,7 +100,17 @@ case svn_wc_notify_exists: nb->received_some_change = TRUE; - if ((err = svn_cmdline_printf(pool, "E %s\n", path_local))) + if (n->content_state == svn_wc_notify_state_conflicted) + statchar_buf[0] = 'C'; + else + statchar_buf[0] = 'E'; + + if (n->prop_state == svn_wc_notify_state_conflicted) + statchar_buf[1] = 'C'; + else if (n->prop_state == svn_wc_notify_state_merged) + statchar_buf[1] = 'G'; + + if ((err = svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local))) goto print_error; break; Index: subversion/tests/cmdline/checkout_tests.py =================================================================== --- subversion/tests/cmdline/checkout_tests.py (revision 21505) +++ subversion/tests/cmdline/checkout_tests.py (working copy) @@ -283,8 +283,8 @@ "--force", sbox.repo_url, other_wc_dir) - test_stderr("svn: Failed to forcibly add directory '.*A': a versioned " \ - "directory of the same name already exists\n", serr) + test_stderr("svn: Failed to add directory '.*A': a versioned directory " \ + "of the same name already exists", serr) #---------------------------------------------------------------------- # Ensure that an import followed by a checkout in place works correctly. @@ -474,7 +474,256 @@ expected_wc) #---------------------------------------------------------------------- +def co_with_obstructing_local_adds(sbox): + "co handles obstructing paths scheduled for add" + sbox.build() + wc_dir = sbox.wc_dir + + # Make a backup copy of the working copy + wc_backup = sbox.add_wc_path('backup') + svntest.actions.duplicate_dir(wc_dir, wc_backup) + + # Add files and dirs to the repos via the first WC. Each of these + # will be added to the backup WC via an update: + # + # A/B/upsilon: Identical to the file scheduled for addition in + # the backup WC. + # + # A/C/nu: A "normal" add, won't exist in the backup WC. + # + # A/D/kappa: Conflicts with the file scheduled for addition in + # the backup WC. + # + # A/D/H/I: New dirs that will also be scheduled for addition + # A/D/H/I/J: in the backup WC. + # A/D/H/I/K: + # + # A/D/H/I/L: A "normal" dir add, won't exist in the backup WC. + # + # A/D/H/I/K/xi: Identical to the file scheduled for addition in + # the backup WC. + # + # A/D/H/I/K/eta: Conflicts with the file scheduled for addition in + # the backup WC. + upsilon_path = os.path.join(wc_dir, 'A', 'B', 'upsilon') + svntest.main.file_append(upsilon_path, "This is the file 'upsilon'\n") + nu_path = os.path.join(wc_dir, 'A', 'C', 'nu') + svntest.main.file_append(nu_path, "This is the file 'nu'\n") + kappa_path = os.path.join(wc_dir, 'A', 'D', 'kappa') + svntest.main.file_append(kappa_path, "This is REPOS file 'kappa'\n") + I_path = os.path.join(wc_dir, 'A', 'D', 'H', 'I') + os.mkdir(I_path) + J_path = os.path.join(I_path, 'J') + os.mkdir(J_path) + K_path = os.path.join(I_path, 'K') + os.mkdir(K_path) + L_path = os.path.join(I_path, 'L') + os.mkdir(L_path) + xi_path = os.path.join(K_path, 'xi') + svntest.main.file_append(xi_path, "This is file 'xi'\n") + eta_path = os.path.join(K_path, 'eta') + svntest.main.file_append(eta_path, "This is REPOS file 'eta'\n") + svntest.main.run_svn(None, 'add', upsilon_path, nu_path, + kappa_path, I_path) + + # Created expected output tree for 'svn ci' + expected_output = wc.State(wc_dir, { + 'A/B/upsilon' : Item(verb='Adding'), + 'A/C/nu' : Item(verb='Adding'), + 'A/D/kappa' : Item(verb='Adding'), + 'A/D/H/I' : Item(verb='Adding'), + 'A/D/H/I/J' : Item(verb='Adding'), + 'A/D/H/I/K' : Item(verb='Adding'), + 'A/D/H/I/K/xi' : Item(verb='Adding'), + 'A/D/H/I/K/eta' : Item(verb='Adding'), + 'A/D/H/I/L' : Item(verb='Adding'), + }) + + # Create expected status tree. + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'A/B/upsilon' : Item(status=' ', wc_rev=2), + 'A/C/nu' : Item(status=' ', wc_rev=2), + 'A/D/kappa' : Item(status=' ', wc_rev=2), + 'A/D/H/I' : Item(status=' ', wc_rev=2), + 'A/D/H/I/J' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K/xi' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K/eta' : Item(status=' ', wc_rev=2), + 'A/D/H/I/L' : Item(status=' ', wc_rev=2), + }) + + # Commit. + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status, None, + None, None, None, None, wc_dir) + + # Create various paths scheduled for addition which will obstruct + # the adds coming from the repos. + upsilon_backup_path = os.path.join(wc_backup, 'A', 'B', 'upsilon') + svntest.main.file_append(upsilon_backup_path, + "This is the file 'upsilon'\n") + kappa_backup_path = os.path.join(wc_backup, 'A', 'D', 'kappa') + svntest.main.file_append(kappa_backup_path, + "This is WC file 'kappa'\n") + I_backup_path = os.path.join(wc_backup, 'A', 'D', 'H', 'I') + os.mkdir(I_backup_path) + J_backup_path = os.path.join(I_backup_path, 'J') + os.mkdir(J_backup_path) + K_backup_path = os.path.join(I_backup_path, 'K') + os.mkdir(K_backup_path) + xi_backup_path = os.path.join(K_backup_path, 'xi') + svntest.main.file_append(xi_backup_path, "This is file 'xi'\n") + eta_backup_path = os.path.join(K_backup_path, 'eta') + svntest.main.file_append(eta_backup_path, "This is WC file 'eta'\n") + svntest.main.run_svn(None, 'add', + upsilon_backup_path, + kappa_backup_path, + I_backup_path) + + # Create expected output tree for an update of the wc_backup. + expected_output = wc.State(wc_backup, { + 'A/B/upsilon' : Item(status='E '), + 'A/C/nu' : Item(status='A '), + 'A/D/H/I' : Item(status='E '), + 'A/D/H/I/J' : Item(status='E '), + 'A/D/H/I/K' : Item(status='E '), + 'A/D/H/I/K/xi' : Item(status='E '), + 'A/D/H/I/K/eta' : Item(status='C '), + 'A/D/H/I/L' : Item(status='A '), + 'A/D/kappa' : Item(status='C '), + }) + + # Create expected disk for update of wc_backup. + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + 'A/B/upsilon' : Item("This is the file 'upsilon'\n"), + 'A/C/nu' : Item("This is the file 'nu'\n"), + 'A/D/H/I' : Item(), + 'A/D/H/I/J' : Item(), + 'A/D/H/I/K' : Item(), + 'A/D/H/I/K/xi' : Item("This is file 'xi'\n"), + 'A/D/H/I/K/eta' : Item("""<<<<<<< .mine +This is WC file 'eta' +======= +This is REPOS file 'eta' +>>>>>>> .r2 +"""), + 'A/D/H/I/L' : Item(), + 'A/D/kappa' : Item("""<<<<<<< .mine +This is WC file 'kappa' +======= +This is REPOS file 'kappa' +>>>>>>> .r2 +"""), + }) + + # Create expected status tree for the update. Since the obstructing + # kappa and upsilon differ from the repos, they should show as modified. + expected_status = svntest.actions.get_virginal_state(wc_backup, 2) + expected_status.add({ + 'A/B/upsilon' : Item(status=' ', wc_rev=2), + 'A/C/nu' : Item(status=' ', wc_rev=2), + 'A/D/H/I' : Item(status=' ', wc_rev=2), + 'A/D/H/I/J' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K/xi' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K/eta' : Item(status='C ', wc_rev=2), + 'A/D/H/I/L' : Item(status=' ', wc_rev=2), + 'A/D/kappa' : Item(status='C ', wc_rev=2), + }) + + # "Extra" files that we expect to result from the conflicts. + extra_files = ['eta\.r0', 'eta\.r2', 'eta\.mine', + 'kappa\.r0', 'kappa\.r2', 'kappa\.mine'] + + # Perform forced update and check the results in three ways. + # We use --force here because run_and_verify_checkout() will delete + # wc_backup before performing the checkout otherwise. + svntest.actions.run_and_verify_checkout(sbox.repo_url, wc_backup, + expected_output, expected_disk, + svntest.tree.detect_conflict_files, + extra_files, None, None, + '--force') + + svntest.actions.run_and_verify_status(wc_backup, expected_status) + + # Some obstructions are still not permitted: + # + # Test that file and dir obstructions scheduled for addition *with* + # history fail when update tries to add the same path. + + # URL to URL copy of A/D/G to A/M. + G_URL = sbox.repo_url + '/A/D/G' + M_URL = sbox.repo_url + '/A/M' + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', G_URL, M_URL, '-m', '') + + # WC to WC copy of A/D/H to A/M, M now scheduled for addition with + # history in WC and pending addition from the repos. + H_path = os.path.join(wc_dir, 'A', 'D', 'H') + A_path = os.path.join(wc_dir, 'A') + M_path = os.path.join(wc_dir, 'A', 'M') + + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', H_path, M_path) + + # URL to URL copy of A/D/H/omega to omicron. + omega_URL = sbox.repo_url + '/A/D/H/omega' + omicron_URL = sbox.repo_url + '/omicron' + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', omega_URL, omicron_URL, + '-m', '') + + # WC to WC copy of A/D/H/chi to omicron, omicron now scheduled for + # addition with history in WC and pending addition from the repos. + chi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'chi') + omicron_path = os.path.join(wc_dir, 'omicron') + + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', chi_path, + omicron_path) + + # Try to co M's Parent. + sout, serr = svntest.actions.run_and_verify_svn("Checkout XPASS", + [], SVNAnyOutput, + 'co', sbox.repo_url + '/A', + A_path) + + test_stderr("svn: Failed to add directory '.*M': a versioned " \ + "directory of the same name already exists\n", serr) + + # --force shouldn't help either. + sout, serr = svntest.actions.run_and_verify_svn("Checkout XPASS", + [], SVNAnyOutput, + 'co', sbox.repo_url + '/A', + A_path, '--force') + + test_stderr("svn: Failed to add directory '.*M': a versioned " \ + "directory of the same name already exists\n", serr) + + # Try to co omicron's parent, non-recusively so as not to + # try and update M first. + sout, serr = svntest.actions.run_and_verify_svn("Checkout XPASS", + [], SVNAnyOutput, + 'co', sbox.repo_url, + wc_dir, '-N') + + test_stderr("svn: Failed to add file '.*omicron': a file of the same " \ + "name is already scheduled for addition with history\n", serr) + + # Again, --force shouldn't matter. + sout, serr = svntest.actions.run_and_verify_svn("Checkout XPASS", + [], SVNAnyOutput, + 'co', sbox.repo_url, + wc_dir, '-N', '--force') + + test_stderr("svn: Failed to add file '.*omicron': a file of the same " \ + "name is already scheduled for addition with history\n", serr) + +#---------------------------------------------------------------------- + # list all tests here, starting with None: test_list = [ None, checkout_with_obstructions, @@ -489,6 +738,7 @@ checkout_creates_intermediate_folders, checkout_peg_rev, XFail(checkout_peg_rev_date), + co_with_obstructing_local_adds, ] if __name__ == "__main__": Index: subversion/tests/cmdline/switch_tests.py =================================================================== --- subversion/tests/cmdline/switch_tests.py (revision 21505) +++ subversion/tests/cmdline/switch_tests.py (working copy) @@ -1283,12 +1283,109 @@ 'A', 'D', 'G'), sbox.repo_url + "/A/D/H", None, None, None, - ".*Failed to forcibly add " + \ + ".*Failed to add " + \ "directory .*: a versioned " + \ "directory of the same name " + \ "already exists\n", None, None, None, None, 0, '--force') +def switch_with_obstructing_local_adds(sbox): + "switch tolerates WC adds" + sbox.build() + + # Dir obstruction scheduled for addition without history. + G_path = os.path.join(sbox.wc_dir, 'A', 'B', 'F', 'G') + os.mkdir(G_path) + + # File obstructions scheduled for addition without history. + # Contents identical to additions from switch. + gamma_copy_path = os.path.join(sbox.wc_dir, 'A', 'B', 'F', 'gamma') + shutil.copyfile(os.path.join(sbox.wc_dir, 'A', 'D', 'gamma'), + gamma_copy_path) + shutil.copyfile(os.path.join(sbox.wc_dir, 'A', 'D', 'G', 'tau'), + os.path.join(sbox.wc_dir, 'A', 'B', 'F', 'G', 'tau')) + + # File obstruction scheduled for addition without history. + # Contents conflict with addition from switch. + pi_path = os.path.join(sbox.wc_dir, 'A', 'B', 'F', 'G', 'pi') + svntest.main.file_write(pi_path, + "This is the OBSTRUCTING file 'pi'.\n") + + # Non-obstructing dir and file scheduled for addition without history. + I_path = os.path.join(sbox.wc_dir, 'A', 'B', 'F', 'I') + os.mkdir(I_path) + upsilon_path = os.path.join(G_path, 'upsilon') + svntest.main.file_write(upsilon_path, + "This is the unversioned file 'upsilon'.\n") + + # Add the above obstructions. + svntest.actions.run_and_verify_svn("Add error:", None, [], + 'add', G_path, I_path, + gamma_copy_path) + + # Setup expected results of switch. + expected_output = svntest.wc.State(sbox.wc_dir, { + "A/B/F/gamma" : Item(status='E '), + "A/B/F/G" : Item(status='E '), + "A/B/F/G/pi" : Item(status='C '), + "A/B/F/G/rho" : Item(status='A '), + "A/B/F/G/tau" : Item(status='E '), + "A/B/F/H" : Item(status='A '), + "A/B/F/H/chi" : Item(status='A '), + "A/B/F/H/omega" : Item(status='A '), + "A/B/F/H/psi" : Item(status='A '), + }) + + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + "A/B/F/gamma" : Item("This is the file 'gamma'.\n"), + "A/B/F/G" : Item(), + "A/B/F/G/pi" : Item("""<<<<<<< .mine +This is the OBSTRUCTING file 'pi'. +======= +This is the file 'pi'. +>>>>>>> .r1 +"""), + "A/B/F/G/rho" : Item("This is the file 'rho'.\n"), + "A/B/F/G/tau" : Item("This is the file 'tau'.\n"), + "A/B/F/G/upsilon" : Item("This is the unversioned file 'upsilon'.\n"), + "A/B/F/H" : Item(), + "A/B/F/H/chi" : Item("This is the file 'chi'.\n"), + "A/B/F/H/omega" : Item("This is the file 'omega'.\n"), + "A/B/F/H/psi" : Item("This is the file 'psi'.\n"), + "A/B/F/I" : Item(), + }) + expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_status.tweak('A/B/F', switched='S') + expected_status.add({ + "A/B/F/gamma" : Item(status=' ', wc_rev=1), + "A/B/F/G" : Item(status=' ', wc_rev=1), + "A/B/F/G/pi" : Item(status='C ', wc_rev=1), + "A/B/F/G/rho" : Item(status=' ', wc_rev=1), + "A/B/F/G/tau" : Item(status=' ', wc_rev=1), + "A/B/F/G/upsilon" : Item(status='A ', wc_rev=0), + "A/B/F/H" : Item(status=' ', wc_rev=1), + "A/B/F/H/chi" : Item(status=' ', wc_rev=1), + "A/B/F/H/omega" : Item(status=' ', wc_rev=1), + "A/B/F/H/psi" : Item(status=' ', wc_rev=1), + "A/B/F/I" : Item(status='A ', wc_rev=0), + }) + + # "Extra" files that we expect to result from the conflicts. + extra_files = ['pi\.r0', 'pi\.r1', 'pi\.mine'] + + # Do the switch and check the results in three ways. + F_path = os.path.join(sbox.wc_dir, 'A', 'B', 'F') + D_url = svntest.main.current_repo_url + '/A/D' + + svntest.actions.run_and_verify_switch(sbox.wc_dir, F_path, D_url, + expected_output, + expected_disk, + expected_status, + None, + svntest.tree.detect_conflict_files, + extra_files, None, None, 0) + ######################################################################## # Run the tests Index: subversion/tests/cmdline/update_tests.py =================================================================== --- subversion/tests/cmdline/update_tests.py (revision 21505) +++ subversion/tests/cmdline/update_tests.py (working copy) @@ -998,7 +998,8 @@ for n in range(2): out, err = svntest.main.run_svn(1, 'up', wc_dir) for line in err: - if line.find("object of the same name already exists") != -1: + if line.find("an unversioned directory of the same " \ + "name already exists") != -1: break else: raise svntest.Failure @@ -2107,7 +2108,7 @@ [], "co", I_url, I_path) svntest.actions.run_and_verify_update(C_Path, None, None, None, - ".*Failed to forcibly add " + \ + ".*Failed to add " + \ "directory.*a versioned directory " + \ "of the same name already exists", None, None, None, None, 0, C_Path, @@ -2229,6 +2230,317 @@ # cleanup the virtual drive os.popen3('subst /D ' + drive +': ', 't') +#---------------------------------------------------------------------- +def update_with_obstructing_additions(sbox): + "update handles obstructing paths scheduled for add" + + sbox.build() + wc_dir = sbox.wc_dir + + # Make a backup copy of the working copy + wc_backup = sbox.add_wc_path('backup') + svntest.actions.duplicate_dir(wc_dir, wc_backup) + + # Add files and dirs to the repos via the first WC. Each of these + # will be added to the backup WC via an update: + # + # A/B/upsilon: Identical to the file scheduled for addition in + # the backup WC. + # + # A/C/nu: A "normal" add, won't exist in the backup WC. + # + # A/D/kappa: Textual and property conflict with the file scheduled + # for addition in the backup WC. + # + # A/D/epsilon: Textual conflict with the file scheduled for addition. + # + # A/D/zeta: Prop conflict with the file scheduled for addition. + # + # Three new dirs that will also be scheduled for addition: + # A/D/H/I: No props on either WC or REPOS. + # A/D/H/I/J: Prop conflict with the scheduled add. + # A/D/H/I/K: Same (mergeable) prop on WC and REPOS. + # + # A/D/H/I/K/xi: Identical to the file scheduled for addition in + # the backup WC. No props. + # + # A/D/H/I/L: A "normal" dir add, won't exist in the backup WC. + # + # A/D/H/I/J/eta: Conflicts with the file scheduled for addition in + # the backup WC. No props. + upsilon_path = os.path.join(wc_dir, 'A', 'B', 'upsilon') + svntest.main.file_append(upsilon_path, "This is the file 'upsilon'\n") + nu_path = os.path.join(wc_dir, 'A', 'C', 'nu') + svntest.main.file_append(nu_path, "This is the file 'nu'\n") + kappa_path = os.path.join(wc_dir, 'A', 'D', 'kappa') + svntest.main.file_append(kappa_path, "This is REPOS file 'kappa'\n") + epsilon_path = os.path.join(wc_dir, 'A', 'D', 'epsilon') + svntest.main.file_append(epsilon_path, "This is REPOS file 'epsilon'\n") + zeta_path = os.path.join(wc_dir, 'A', 'D', 'zeta') + svntest.main.file_append(zeta_path, "This is the file 'zeta'\n") + I_path = os.path.join(wc_dir, 'A', 'D', 'H', 'I') + os.mkdir(I_path) + J_path = os.path.join(I_path, 'J') + os.mkdir(J_path) + K_path = os.path.join(I_path, 'K') + os.mkdir(K_path) + L_path = os.path.join(I_path, 'L') + os.mkdir(L_path) + xi_path = os.path.join(K_path, 'xi') + svntest.main.file_append(xi_path, "This is the file 'xi'\n") + eta_path = os.path.join(J_path, 'eta') + svntest.main.file_append(eta_path, "This is REPOS file 'eta'\n") + svntest.main.run_svn(None, 'add', upsilon_path, nu_path, + kappa_path, epsilon_path, zeta_path, I_path) + + # Set props that will conflict with scheduled adds. + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-REPOS', + kappa_path) + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-REPOS', + zeta_path) + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-REPOS', + J_path) + + # Set prop that will match with scheduled add. + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME', + epsilon_path) + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME', + K_path) + + # Created expected output tree for 'svn ci' + expected_output = wc.State(wc_dir, { + 'A/B/upsilon' : Item(verb='Adding'), + 'A/C/nu' : Item(verb='Adding'), + 'A/D/kappa' : Item(verb='Adding'), + 'A/D/epsilon' : Item(verb='Adding'), + 'A/D/zeta' : Item(verb='Adding'), + 'A/D/H/I' : Item(verb='Adding'), + 'A/D/H/I/J' : Item(verb='Adding'), + 'A/D/H/I/J/eta' : Item(verb='Adding'), + 'A/D/H/I/K' : Item(verb='Adding'), + 'A/D/H/I/K/xi' : Item(verb='Adding'), + 'A/D/H/I/L' : Item(verb='Adding'), + }) + + # Create expected status tree. + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'A/B/upsilon' : Item(status=' ', wc_rev=2), + 'A/C/nu' : Item(status=' ', wc_rev=2), + 'A/D/kappa' : Item(status=' ', wc_rev=2), + 'A/D/epsilon' : Item(status=' ', wc_rev=2), + 'A/D/zeta' : Item(status=' ', wc_rev=2), + 'A/D/H/I' : Item(status=' ', wc_rev=2), + 'A/D/H/I/J' : Item(status=' ', wc_rev=2), + 'A/D/H/I/J/eta' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K/xi' : Item(status=' ', wc_rev=2), + 'A/D/H/I/L' : Item(status=' ', wc_rev=2), + }) + + # Commit. + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status, None, + None, None, None, None, wc_dir) + + # Create various paths scheduled for addition which will obstruct + # the adds coming from the repos. + upsilon_backup_path = os.path.join(wc_backup, 'A', 'B', 'upsilon') + svntest.main.file_append(upsilon_backup_path, + "This is the file 'upsilon'\n") + kappa_backup_path = os.path.join(wc_backup, 'A', 'D', 'kappa') + svntest.main.file_append(kappa_backup_path, + "This is WC file 'kappa'\n") + epsilon_backup_path = os.path.join(wc_backup, 'A', 'D', 'epsilon') + svntest.main.file_append(epsilon_backup_path, + "This is WC file 'epsilon'\n") + zeta_backup_path = os.path.join(wc_backup, 'A', 'D', 'zeta') + svntest.main.file_append(zeta_backup_path, "This is the file 'zeta'\n") + I_backup_path = os.path.join(wc_backup, 'A', 'D', 'H', 'I') + os.mkdir(I_backup_path) + J_backup_path = os.path.join(I_backup_path, 'J') + os.mkdir(J_backup_path) + K_backup_path = os.path.join(I_backup_path, 'K') + os.mkdir(K_backup_path) + xi_backup_path = os.path.join(K_backup_path, 'xi') + svntest.main.file_append(xi_backup_path, "This is the file 'xi'\n") + eta_backup_path = os.path.join(J_backup_path, 'eta') + svntest.main.file_append(eta_backup_path, "This is WC file 'eta'\n") + + svntest.main.run_svn(None, 'add', upsilon_backup_path, kappa_backup_path, + epsilon_backup_path, zeta_backup_path, I_backup_path) + + # Set prop that will conflict with add from repos. + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-WC', + kappa_backup_path) + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-WC', + zeta_backup_path) + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-WC', + J_backup_path) + + # Set prop that will match add from repos. + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME', + epsilon_backup_path) + svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME', + K_backup_path) + + # Create expected output tree for an update of the wc_backup. + expected_output = wc.State(wc_backup, { + 'A/B/upsilon' : Item(status='E '), + 'A/C/nu' : Item(status='A '), + 'A/D/H/I' : Item(status='E '), + 'A/D/H/I/J' : Item(status='EC'), + 'A/D/H/I/J/eta' : Item(status='C '), + 'A/D/H/I/K' : Item(status='EG'), + 'A/D/H/I/K/xi' : Item(status='E '), + 'A/D/H/I/L' : Item(status='A '), + 'A/D/kappa' : Item(status='CC'), + 'A/D/epsilon' : Item(status='CG'), + 'A/D/zeta' : Item(status='EC'), + }) + + # Create expected disk for update of wc_backup. + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + 'A/B/upsilon' : Item("This is the file 'upsilon'\n"), + 'A/C/nu' : Item("This is the file 'nu'\n"), + 'A/D/H/I' : Item(), + 'A/D/H/I/J' : Item(props={'propname1' : 'propval-WC'}), + 'A/D/H/I/J/eta' : Item("""<<<<<<< .mine +This is WC file 'eta' +======= +This is REPOS file 'eta' +>>>>>>> .r2 +"""), + 'A/D/H/I/K' : Item(props={'propname1' : 'propval-SAME'}), + 'A/D/H/I/K/xi' : Item("This is the file 'xi'\n"), + 'A/D/H/I/L' : Item(), + 'A/D/kappa' : Item("""<<<<<<< .mine +This is WC file 'kappa' +======= +This is REPOS file 'kappa' +>>>>>>> .r2 +""", props={'propname1' : 'propval-WC'}), + 'A/D/epsilon' : Item("""<<<<<<< .mine +This is WC file 'epsilon' +======= +This is REPOS file 'epsilon' +>>>>>>> .r2 +""", props={'propname1' : 'propval-SAME'}), + 'A/D/zeta' : Item("This is the file 'zeta'\n", + props={'propname1' : 'propval-WC'}), + }) + + # Create expected status tree for the update. Since the obstructing + # kappa and upsilon differ from the repos, they should show as modified. + expected_status = svntest.actions.get_virginal_state(wc_backup, 2) + expected_status.add({ + 'A/B/upsilon' : Item(status=' ', wc_rev=2), + 'A/C/nu' : Item(status=' ', wc_rev=2), + 'A/D/H/I' : Item(status=' ', wc_rev=2), + 'A/D/H/I/J' : Item(status=' C', wc_rev=2), + 'A/D/H/I/J/eta' : Item(status='C ', wc_rev=2), + 'A/D/H/I/K' : Item(status=' ', wc_rev=2), + 'A/D/H/I/K/xi' : Item(status=' ', wc_rev=2), + 'A/D/H/I/L' : Item(status=' ', wc_rev=2), + 'A/D/kappa' : Item(status='CC', wc_rev=2), + 'A/D/epsilon' : Item(status='C ', wc_rev=2), + 'A/D/zeta' : Item(status=' C', wc_rev=2), + }) + + # "Extra" files that we expect to result from the conflicts. + extra_files = ['eta\.r0', 'eta\.r2', 'eta\.mine', + 'kappa\.r0', 'kappa\.r2', 'kappa\.mine', + 'epsilon\.r0', 'epsilon\.r2', 'epsilon\.mine', + 'kappa.prej', 'zeta.prej', 'dir_conflicts.prej'] + + # Perform forced update and check the results in three + # ways (including props). + svntest.actions.run_and_verify_update(wc_backup, + expected_output, + expected_disk, + expected_status, + None, + svntest.tree.detect_conflict_files, + extra_files, None, None, 1, + wc_backup) + + # Some obstructions are still not permitted: + # + # Test that file and dir obstructions scheduled for addition *with* + # history fail when update tries to add the same path. + + # URL to URL copy of A/D/G to A/M. + G_URL = sbox.repo_url + '/A/D/G' + M_URL = sbox.repo_url + '/A/M' + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', G_URL, M_URL, '-m', '') + + # WC to WC copy of A/D/H to A/M, M now scheduled for addition with + # history in WC and pending addition from the repos. + H_path = os.path.join(wc_dir, 'A', 'D', 'H') + A_path = os.path.join(wc_dir, 'A') + M_path = os.path.join(wc_dir, 'A', 'M') + + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', H_path, M_path) + + # URL to URL copy of A/D/H/omega to omicron. + omega_URL = sbox.repo_url + '/A/D/H/omega' + omicron_URL = sbox.repo_url + '/omicron' + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', omega_URL, omicron_URL, + '-m', '') + + # WC to WC copy of A/D/H/chi to omicron, omicron now scheduled for + # addition with history in WC and pending addition from the repos. + chi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'chi') + omicron_path = os.path.join(wc_dir, 'omicron') + + svntest.actions.run_and_verify_svn("Copy error:", None, [], + 'cp', chi_path, + omicron_path) + + # Try to update M's Parent. + svntest.actions.run_and_verify_update(A_path, expected_output, + expected_disk, expected_status, + "svn: Failed to add " \ + "directory '.*M': a versioned " \ + "directory of the same name " \ + "already exists", + None, None, None, None, 0) + + # --force shouldn't help either. + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + "svn: Failed to add " \ + "directory '.*M': a versioned " \ + "directory of the same name " \ + "already exists", + None, None, None, None, 0, + A_path, '--force') + + # Try to update omicron's parent, non-recusively so as not to + # try and update M first. + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + "Failed to add file '.*omicron': " \ + "a file of the same name is " \ + "already scheduled for addition " \ + "with history", + None, None, None, None, 0, + wc_dir, '-N') + + # Again, --force shouldn't matter. + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + "Failed to add file '.*omicron': " \ + "a file of the same name is " \ + "already scheduled for addition " \ + "with history", + None, None, None, None, 0, + wc_dir, '-N', '--force') + ######################################################################## # Run the tests @@ -2267,6 +2579,7 @@ forced_update, forced_update_failures, update_wc_on_windows_drive, + update_with_obstructing_additions, ] if __name__ == '__main__':