Index: build.conf =================================================================== --- build.conf (revision 19507) +++ build.conf (working copy) @@ -3,7 +3,7 @@ # ###################################################################### # -# Copyright (c) 2000-2004 CollabNet. All rights reserved. +# Copyright (c) 2000-2006 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms @@ -60,6 +60,7 @@ subversion/tests/libsvn_subr/target-test.py subversion/tests/cmdline/getopt_tests.py subversion/tests/cmdline/basic_tests.py + subversion/tests/cmdline/checkout_tests.py subversion/tests/cmdline/commit_tests.py subversion/tests/cmdline/update_tests.py subversion/tests/cmdline/switch_tests.py Index: subversion/include/svn_client.h =================================================================== --- subversion/include/svn_client.h (revision 19507) +++ subversion/include/svn_client.h (working copy) @@ -691,6 +691,13 @@ * just the directory represented by @a URL and its immediate * non-directory children, but none of its child directories (if any). * + * If @a force is @c TRUE then permit the checkout to tolerate an existing + * local tree rooted at @a PATH even if some paths in the local tree + * are identical to those in @a URL. File paths that collide will + * effectively treat the local file as a user modification to the pristine + * checkout. If @a force is @c FALSE then the checkout will abort if there + * are any colliding paths. + * * If @a URL refers to a file rather than a directory, return the * error SVN_ERR_UNSUPPORTED_FEATURE. If @a URL does not exist, * return the error SVN_ERR_RA_ILLEGAL_URL. @@ -700,6 +707,25 @@ * @since New in 1.2. */ svn_error_t * +svn_client_checkout3(svn_revnum_t *result_rev, + const char *URL, + const char *path, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_boolean_t recurse, + svn_boolean_t ignore_externals, + svn_boolean_t force, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + + +/** + * Similar to svn_client_checkout3() but with the force parameter always + * set to @c FALSE. + * + * @deprecated Provided for backward compatibility with the 1.3 API. + */ +svn_error_t * svn_client_checkout2(svn_revnum_t *result_rev, const char *URL, const char *path, Index: subversion/include/svn_wc.h =================================================================== --- subversion/include/svn_wc.h (revision 19507) +++ subversion/include/svn_wc.h (working copy) @@ -640,7 +640,11 @@ svn_wc_notify_failed_lock, /** Failed to unlock a path. @since New in 1.2. */ - svn_wc_notify_failed_unlock + svn_wc_notify_failed_unlock, + + /** @since New in 1.4. Took over a path during a forced checkout. */ + svn_wc_notify_co_takeover + } svn_wc_notify_action_t; @@ -2472,8 +2476,34 @@ * have their working timestamp set to the last-committed-time. If * FALSE, the working files will be touched with the 'now' time. * - * @since New in 1.2. + * If @a forced_checkout is TRUE, then the @a *editor and @a *edit_baton + * returned are for a checkout with the --force option, if @c FALSE these + * are for a co without --force or an update. + * + * @since New in 1.4. */ +svn_error_t *svn_wc_get_update_editor3(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + svn_boolean_t use_commit_times, + svn_boolean_t recurse, + svn_boolean_t forced_checkout, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const char *diff3_cmd, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *ti, + apr_pool_t *pool); + + +/* Similar to svn_wc_get_update_editor3() but with the forced_checkout + * parameter always set to @c FALSE. + * + * @deprecated Provided for backward compatibility with the 1.3 API. + */ svn_error_t *svn_wc_get_update_editor2(svn_revnum_t *target_revision, svn_wc_adm_access_t *anchor, const char *target, Index: subversion/libsvn_client/checkout.c =================================================================== --- subversion/libsvn_client/checkout.c (revision 19507) +++ subversion/libsvn_client/checkout.c (working copy) @@ -2,7 +2,7 @@ * checkout.c: wrappers around wc checkout functionality * * ==================================================================== - * Copyright (c) 2000-2004 CollabNet. All rights reserved. + * Copyright (c) 2000-2006 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -49,6 +49,7 @@ const svn_opt_revision_t *revision, svn_boolean_t recurse, svn_boolean_t ignore_externals, + svn_boolean_t forced_checkout, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -119,6 +120,7 @@ /* Have update fix the incompleteness. */ err = svn_client__update_internal(result_rev, path, revision, recurse, ignore_externals, + forced_checkout, use_sleep, ctx, pool); } else if (kind == svn_node_dir) @@ -135,6 +137,7 @@ repos, revnum, pool)); err = svn_client__update_internal(result_rev, path, revision, recurse, ignore_externals, + forced_checkout, use_sleep, ctx, pool); goto done; } @@ -153,7 +156,8 @@ { err = svn_client__update_internal(result_rev, path, revision, recurse, ignore_externals, - use_sleep, ctx, pool); + forced_checkout, use_sleep, + ctx, pool); } else { @@ -204,6 +208,23 @@ } svn_error_t * +svn_client_checkout3(svn_revnum_t *result_rev, + const char *URL, + const char *path, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_boolean_t recurse, + svn_boolean_t ignore_externals, + svn_boolean_t force, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + return svn_client__checkout_internal(result_rev, URL, path, peg_revision, + revision, recurse, ignore_externals, + force, NULL, ctx, pool); +} + +svn_error_t * svn_client_checkout2(svn_revnum_t *result_rev, const char *URL, const char *path, @@ -216,7 +237,7 @@ { return svn_client__checkout_internal(result_rev, URL, path, peg_revision, revision, recurse, ignore_externals, - NULL, ctx, pool); + FALSE, NULL, ctx, pool); } svn_error_t * @@ -233,6 +254,6 @@ peg_revision.kind = svn_opt_revision_unspecified; return svn_client__checkout_internal(result_rev, URL, path, &peg_revision, - revision, recurse, FALSE, NULL, + revision, recurse, FALSE, FALSE, NULL, ctx, pool); } Index: subversion/libsvn_client/client.h =================================================================== --- subversion/libsvn_client/client.h (revision 19507) +++ subversion/libsvn_client/client.h (working copy) @@ -323,13 +323,17 @@ function will sleep before returning to ensure timestamp integrity. If TIMESTAMP_SLEEP is not NULL then the function will not sleep but will set *TIMESTAMP_SLEEP to TRUE if a sleep is required, and will - not change *TIMESTAMP_SLEEP if no sleep is required. */ + not change *TIMESTAMP_SLEEP if no sleep is required. If + FORCED_CHECKOUT is TRUE this is part of a checkout with the --force + option. If it is FALSE it is a checkout without the --force option, + an update, or a switch. */ svn_error_t * svn_client__update_internal(svn_revnum_t *result_rev, const char *path, const svn_opt_revision_t *revision, svn_boolean_t recurse, svn_boolean_t ignore_externals, + svn_boolean_t forced_checkout, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool); @@ -341,7 +345,9 @@ timestamp integrity. If TIMESTAMP_SLEEP is not NULL then the function will not sleep but will set *TIMESTAMP_SLEEP to TRUE if a sleep is required, and will not change *TIMESTAMP_SLEEP if no sleep - is required. */ + is required. If FORCED_CHECKOUT is TRUE this is part of a checkout + with the --force option. If it is FALSE it is a checkout without the + --force option. */ svn_error_t * svn_client__checkout_internal(svn_revnum_t *result_rev, const char *URL, @@ -350,6 +356,7 @@ const svn_opt_revision_t *revision, svn_boolean_t recurse, svn_boolean_t ignore_externals, + svn_boolean_t forced_checkout, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool); Index: subversion/libsvn_client/copy.c =================================================================== --- subversion/libsvn_client/copy.c (revision 19507) +++ subversion/libsvn_client/copy.c (working copy) @@ -831,7 +831,7 @@ { SVN_ERR(svn_client__checkout_internal (NULL, src_url, dst_path, &revision, &revision, - TRUE, FALSE, NULL, ctx, pool)); + TRUE, FALSE, FALSE, NULL, ctx, pool)); if ((revision.kind == svn_opt_revision_head) && same_repositories) { Index: subversion/libsvn_client/externals.c =================================================================== --- subversion/libsvn_client/externals.c (revision 19507) +++ subversion/libsvn_client/externals.c (working copy) @@ -198,8 +198,9 @@ if (strcmp(entry->url, url) == 0) { SVN_ERR(svn_client__update_internal(NULL, path, revision, - TRUE, FALSE, timestamp_sleep, - ctx, pool)); + TRUE, FALSE, FALSE, + timestamp_sleep, ctx, + pool)); return SVN_NO_ERROR; } else if (entry->repos) @@ -268,7 +269,7 @@ /* ... Hello, new hotness. */ SVN_ERR(svn_client__checkout_internal(NULL, url, path, revision, revision, - TRUE, FALSE, timestamp_sleep, + TRUE, FALSE, FALSE, timestamp_sleep, ctx, pool)); return SVN_NO_ERROR; @@ -362,7 +363,7 @@ SVN_ERR(svn_client__checkout_internal(NULL, new_item->url, path, &(new_item->revision), &(new_item->revision), - TRUE, FALSE, + TRUE, FALSE, FALSE, ib->timestamp_sleep, ib->ctx, ib->pool)); } Index: subversion/libsvn_client/update.c =================================================================== --- subversion/libsvn_client/update.c (revision 19507) +++ subversion/libsvn_client/update.c (working copy) @@ -2,7 +2,7 @@ * update.c: wrappers around wc update functionality * * ==================================================================== - * Copyright (c) 2000-2004 CollabNet. All rights reserved. + * Copyright (c) 2000-2006 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -44,6 +44,7 @@ const svn_opt_revision_t *revision, svn_boolean_t recurse, svn_boolean_t ignore_externals, + svn_boolean_t forced_checkout, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -125,8 +126,9 @@ /* Fetch the update editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision later on. */ - SVN_ERR(svn_wc_get_update_editor2(&revnum, adm_access, target, + SVN_ERR(svn_wc_get_update_editor3(&revnum, adm_access, target, use_commit_times, recurse, + forced_checkout, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, diff3_cmd, @@ -221,7 +223,7 @@ break; err = svn_client__update_internal(&result_rev, path, revision, - recurse, ignore_externals, + recurse, ignore_externals, FALSE, &sleep, ctx, subpool); if (err && err->apr_err != SVN_ERR_WC_NOT_DIRECTORY) { @@ -258,5 +260,5 @@ apr_pool_t *pool) { return svn_client__update_internal(result_rev, path, revision, recurse, - FALSE, NULL, ctx, pool); + FALSE, FALSE, NULL, ctx, pool); } Index: subversion/libsvn_wc/update_editor.c =================================================================== --- subversion/libsvn_wc/update_editor.c (revision 19507) +++ subversion/libsvn_wc/update_editor.c (working copy) @@ -84,6 +84,11 @@ /* Was the update-target deleted? This is a special situation. */ svn_boolean_t target_deleted; + + /* Is this an edit_baton for a checkout with the --force option? If so, + existing unversioned files will be accepted as is and not obstruct the + update process. */ + svn_boolean_t forced_checkout; /* Non-null if this is a 'switch' operation. */ const char *switch_url; @@ -515,6 +520,10 @@ /* Set if this file is new. */ svn_boolean_t added; + /* Set if an unversioned file of the same name already existed in + this directory. */ + svn_boolean_t taken_over; + /* 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; @@ -577,6 +586,7 @@ f->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t)); f->bump_info = pb->bump_info; f->added = adding; + f->taken_over = FALSE; f->dir_baton = pb; /* No need to initialize f->digest, since we used pcalloc(). */ @@ -1050,6 +1060,7 @@ 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 takeover = FALSE; /* Semantic check. Either both "copyfrom" args are valid, or they're NULL and SVN_INVALID_REVNUM. A mixture is illegal semantics. */ @@ -1057,15 +1068,78 @@ || ((! copyfrom_path) && (SVN_IS_VALID_REVNUM(copyfrom_revision)))) abort(); - /* There should be nothing with this name. */ SVN_ERR(svn_io_check_path(db->path, &kind, db->pool)); - 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)); + /* Check for obstructions. */ + if (eb->forced_checkout) + { + /* If this is a forced checkout 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 takeover directory '%s': " + "a non-directory object of the same name already exists"), + svn_path_local_style(db->path, pool)); + else if (kind == svn_node_dir) + { + /* ...Ok, it's a directory but if it is already a working copy + it must point to the same URL as the dir we want to add. + Even with --force, checkout doesn't handle obstructing + dirs that point to other URLS. Just wave the white flag in + this case. */ + const svn_wc_entry_t *entry; + svn_wc_adm_access_t *adm_access; + const char *obstructing_path = svn_path_join( + pb->path, svn_path_basename(path, pool), pool); + + /* Test the obstructing dir to see if it's versioned. */ + svn_error_t *err = svn_wc_adm_open3(&adm_access, NULL, + obstructing_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) + { + /* Obstructing dir is not versioned, just need to flag it as + a takeover then we are done here. */ + takeover = TRUE; + svn_error_clear(err); + } + else + { + /* Obstructing dir *is* versioned. */ + takeover = TRUE; + SVN_ERR(svn_wc_entry(&entry, obstructing_path, adm_access, + FALSE, pool)); + SVN_ERR(svn_wc_adm_close(adm_access)); + + if (entry && entry->url + && (strcmp(entry->url, db->new_URL) != 0)) + return svn_error_createf + (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to forcibly add directory '%s': " + "a versioned directory of the same name already " + "exists but which points to a diffent URL"), + svn_path_local_style(db->path, pool)); + } + } + } + else + { + /* There should be nothing with this name if this is not + a forced checkout. */ + 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)) return svn_error_createf @@ -1135,9 +1209,10 @@ if (eb->notify_func) { - svn_wc_notify_t *notify = svn_wc_create_notify(db->path, - svn_wc_notify_update_add, - pool); + svn_wc_notify_t *notify = svn_wc_create_notify( + db->path, + takeover ? svn_wc_notify_co_takeover : svn_wc_notify_update_add, + pool); notify->kind = svn_node_dir; (*eb->notify_func)(eb->notify_baton, notify, pool); } @@ -1461,13 +1536,45 @@ /* Sanity checks. */ - /* If adding, there should be nothing with this name. */ - if (adding && (kind != svn_node_none)) - return svn_error_createf - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Failed to add file '%s': object of the same name already exists"), - svn_path_local_style(fb->path, pool)); - + /* If adding, there should be nothing with this name unless this is + a forced checkout. */ + if (adding) + { + if(kind != svn_node_none) + { + if (eb->forced_checkout) + { + /* 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 (kind != svn_node_file) + return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, + NULL, + _("Failed to takeover file '%s': " + "a non-file object of the same " + "name already exists"), + svn_path_local_style(fb->path, + pool)); + fb->taken_over = TRUE; + } + else + { + return svn_error_createf + (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to add file '%s': object of the same name " + "already exists"), svn_path_local_style(fb->path, pool)); + } + } + else if (eb->forced_checkout && adding && entry + && entry->schedule == svn_wc_schedule_delete) + { + /* During a forced checkout we might encounter the situation where + an already versioned directory is taken over. Further, this + directory might have files scheduled for deletion. We'll flag + these files as taken over too. */ + fb->taken_over = TRUE; + } + } /* sussman sez: If we're trying to add a file that's already in `entries' (but not on disk), that's okay. It's probably because the user deleted the working version and ran 'svn up' as a means @@ -1684,9 +1791,10 @@ applied. */ fb->prop_changed = 1; - /* Special case: if the file is added during a checkout, cache the - last-changed-date propval for future use. */ - if (eb->use_commit_times + /* Special case: if the file is added during a checkout or taken over + during a forced checkout, cache the last-changed-date propval for + future use. */ + if ((eb->use_commit_times || fb->taken_over) && (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)) fb->last_changed_date = apr_pstrdup(fb->pool, value->data); @@ -1840,6 +1948,10 @@ * sensitive to eol translation, keyword substitution, and performing * all actions accumulated to existing LOG_ACCUM. * + * If TAKEN_OVER is TRUE then this is a checkout with the --force option + * and a file already exists at FILE_PATH. In this case no merge is done, + * rather the existing file is left as in in the WC. + * * If HAS_NEW_TEXT_BASE is TRUE, the administrative area must contain the * 'new' text base for NEW_REVISION in the temporary text base location. * The temporary text base will be removed after a successful run of the @@ -1891,6 +2003,7 @@ const char *file_path, svn_revnum_t new_revision, svn_boolean_t has_new_text_base, + svn_boolean_t taken_over, const apr_array_header_t *prop_changes, const char *new_URL, const char *diff3_cmd, @@ -1901,6 +2014,7 @@ const char *new_text_path = NULL; svn_boolean_t is_locally_modified; svn_boolean_t is_replaced = FALSE; + svn_boolean_t is_deleted = FALSE; svn_boolean_t magic_props_changed = FALSE; enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged; svn_wc_notify_lock_state_t local_lock_state; @@ -2024,8 +2138,15 @@ matrix. */ if (new_text_path) { - if (! is_locally_modified && ! is_replaced) + const svn_wc_entry_t *entry; + SVN_ERR(svn_wc_entry(&entry, file_path, adm_access, FALSE, pool)); + if (entry && entry->schedule == svn_wc_schedule_delete) { + /* This file is scheduled for deletion. */ + is_deleted = TRUE; + } + else if (! is_locally_modified && ! is_replaced) + { /* If there are no local mods, who cares whether it's a text or binary file! Just write a log command to overwrite any working file with the new text-base. If newline @@ -2047,6 +2168,17 @@ svn_wc__copy_translate, tmp_txtb, base_name, FALSE, pool)); } + else if (taken_over) /* File of same name already exists. */ + { + svn_wc_entry_t tmp_entry; + + SVN_ERR (svn_time_from_cstring(&(tmp_entry.text_time), + timestamp_string,pool)); + + SVN_ERR(svn_wc__loggy_entry_modify( + &log_accum, adm_access, base_name, &tmp_entry, + SVN_WC__ENTRY_MODIFY_TEXT_TIME, pool)); + } else /* working file exists, and has local mods.*/ { /* Now we need to let loose svn_wc_merge2() to merge the @@ -2107,7 +2239,7 @@ SVN_ERR(svn_wc__loggy_maybe_set_readonly(&log_accum, adm_access, base_name, pool)); - if (new_text_path) + if (new_text_path && !is_deleted) { /* Write out log commands to set up the new text base and its checksum. */ @@ -2136,10 +2268,11 @@ } /* Log commands to handle text-timestamp */ - if (!is_locally_modified) + if (!is_locally_modified && !is_deleted) { - if (timestamp_string) - /* Adjust working copy file */ + if (timestamp_string && !taken_over) + /* Adjust working copy file unless this file already existed + before the forced checkout. */ SVN_ERR(svn_wc__loggy_set_timestamp(&log_accum, adm_access, base_name, timestamp_string, pool)); @@ -2228,6 +2361,7 @@ fb->path, (*eb->target_revision), fb->text_changed, + fb->taken_over, propchanges, fb->new_URL, eb->diff3_cmd, @@ -2250,7 +2384,8 @@ { svn_wc_notify_t *notify = svn_wc_create_notify(fb->path, - fb->added ? svn_wc_notify_update_add + fb->taken_over ? svn_wc_notify_co_takeover + : fb->added ? svn_wc_notify_update_add : svn_wc_notify_update_update, pool); notify->kind = svn_node_file; notify->content_state = content_state; @@ -2341,6 +2476,7 @@ svn_boolean_t use_commit_times, const char *switch_url, svn_boolean_t recurse, + svn_boolean_t forced_checkout, svn_wc_notify_func2_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, @@ -2386,6 +2522,7 @@ eb->diff3_cmd = diff3_cmd; eb->cancel_func = cancel_func; eb->cancel_baton = cancel_baton; + eb->forced_checkout = forced_checkout; /* Construct an editor. */ tree_editor->set_target_revision = set_target_revision; @@ -2417,6 +2554,31 @@ svn_error_t * +svn_wc_get_update_editor3(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + svn_boolean_t use_commit_times, + svn_boolean_t recurse, + svn_boolean_t forced_checkout, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const char *diff3_cmd, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + return make_editor(target_revision, anchor, svn_wc_adm_access_path(anchor), + target, use_commit_times, NULL, recurse, + forced_checkout, notify_func, notify_baton, cancel_func, + cancel_baton, diff3_cmd, editor, edit_baton, + traversal_info, pool); +} + + +svn_error_t * svn_wc_get_update_editor2(svn_revnum_t *target_revision, svn_wc_adm_access_t *anchor, const char *target, @@ -2433,9 +2595,9 @@ apr_pool_t *pool) { return make_editor(target_revision, anchor, svn_wc_adm_access_path(anchor), - target, use_commit_times, NULL, recurse, notify_func, - notify_baton, cancel_func, cancel_baton, diff3_cmd, - editor, edit_baton, traversal_info, pool); + target, use_commit_times, NULL, recurse, FALSE, + notify_func, notify_baton, cancel_func, cancel_baton, + diff3_cmd, editor, edit_baton, traversal_info, pool); } svn_error_t * @@ -2485,7 +2647,7 @@ assert(switch_url); return make_editor(target_revision, anchor, svn_wc_adm_access_path(anchor), - target, use_commit_times, switch_url, recurse, + target, use_commit_times, switch_url, recurse, FALSE, notify_func, notify_baton, cancel_func, cancel_baton, diff3_cmd, editor, edit_baton, traversal_info, pool); } Index: subversion/svn/checkout-cmd.c =================================================================== --- subversion/svn/checkout-cmd.c (revision 19507) +++ subversion/svn/checkout-cmd.c (working copy) @@ -156,12 +156,24 @@ revision.kind = svn_opt_revision_head; } - SVN_ERR(svn_client_checkout2(NULL, true_url, target_dir, + SVN_ERR(svn_client_checkout3(NULL, true_url, target_dir, &peg_revision, &revision, opt_state->nonrecursive ? FALSE : TRUE, opt_state->ignore_externals, + opt_state->force, ctx, subpool)); + + /* If this is a checkout with the --force option it was likely used + to create a WC over an existing local tree. If any of the file + paths in the intial tree collided with paths coming from the + repository then the text-time field for this file in entries is + equal to the committed-date field. This identifies the file as + *potentially* modified. Running cleanup scrubs the entries file + so that WC files with no actual modifications have their text-time + field set the the file's last modified time. */ + if (opt_state->force) + SVN_ERR(svn_client_cleanup(target_dir, ctx, subpool)); } svn_pool_destroy(subpool); Index: subversion/svn/main.c =================================================================== --- subversion/svn/main.c (revision 19507) +++ subversion/svn/main.c (working copy) @@ -231,8 +231,8 @@ " the destination. If multiple URLs are given each will be checked\n" " out into a sub-directory of PATH, with the name of the sub-directory\n" " being the basename of the URL.\n"), - {'r', 'q', 'N', SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt, - svn_cl__ignore_externals_opt} }, + {'r', 'q', 'N', svn_cl__force_opt, SVN_CL__AUTH_OPTIONS, + svn_cl__config_dir_opt, svn_cl__ignore_externals_opt} }, { "cleanup", svn_cl__cleanup, {0}, N_ ("Recursively clean up the working copy, removing locks, resuming\n" Index: subversion/svn/notify.c =================================================================== --- subversion/svn/notify.c (revision 19507) +++ subversion/svn/notify.c (working copy) @@ -2,7 +2,7 @@ * notify.c: feedback handlers for cmdline client. * * ==================================================================== - * Copyright (c) 2000-2004 CollabNet. All rights reserved. + * Copyright (c) 2000-2006 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -90,6 +90,12 @@ goto print_error; break; + case svn_wc_notify_co_takeover: + nb->received_some_change = TRUE; + if ((err = svn_cmdline_printf(pool, "T %s\n", path_local))) + goto print_error; + break; + case svn_wc_notify_restore: if ((err = svn_cmdline_printf(pool, _("Restored '%s'\n"), path_local))) Index: subversion/tests/cmdline/checkout_tests.py =================================================================== --- subversion/tests/cmdline/checkout_tests.py (revision 0) +++ subversion/tests/cmdline/checkout_tests.py (revision 0) @@ -0,0 +1,808 @@ +#!/usr/bin/env python +# +# checkout_tests.py: Testing checkout --force behavior when local +# tree already exits. +# +# Subversion is a tool for revision control. +# See http://subversion.tigris.org for more information. +# +# ==================================================================== +# Copyright (c) 2000-2006 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +###################################################################### + +# General modules +import sys, re, os + +# Our testing module +import svntest +from svntest import wc, SVNAnyOutput + +# (abbreviation) +Item = wc.StateItem + +#---------------------------------------------------------------------- +# Helper function to set up an existing local tree that has paths which +# obstruct with the incoming WC. +# +# Build a sandbox and remove all files and directories (including the +# .svn administrative dirs) from the working copy except for 'iota', +# 'A/', and 'A/mu'. +# +# If MOD_FILES is TRUE make modifications to 'iota' and 'A/mu' that +# conflict with the standard greek tree. +# +# If ADD_UNVERSIONED is TRUE create some files and directories that don't +# exist in the standard greek tree ('sigma', 'A/upsilon', 'A/Z') +def make_local_tree(sbox, mod_files=False, add_unversioned=False): + """Make a local unversioned tree to checkout into.""" + + sbox.build() + + # Remove the WC... + svntest.main.safe_rmtree(sbox.wc_dir) + export_target = sbox.wc_dir + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = sbox.wc_dir + expected_output.desc[""] = Item() + expected_output.tweak(contents=None, status="A ") + + # ... and export an unversioned tree to the same location. + svntest.actions.run_and_verify_export(svntest.main.current_repo_url, + export_target, + expected_output, + svntest.main.greek_state.copy()) + + # Remove everything remaining except for 'iota', 'A/', and 'A/mu'. + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "B")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "C")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "D")) + + if mod_files: + iota_path = os.path.join(sbox.wc_dir, "iota") + mu_path = os.path.join(sbox.wc_dir, "A", "mu") + svntest.main.file_write(iota_path, + "This is the local version of the file 'iota'.\n") + svntest.main.file_write(mu_path, + "This is the local version of the file 'mu'.\n") + + if add_unversioned: + sigma_path = os.path.join(sbox.wc_dir, "sigma") + svntest.main.file_append(sigma_path, "unversioned sigma") + upsilon_path = os.path.join(sbox.wc_dir, "A", "upsilon") + svntest.main.file_append(upsilon_path, "unversioned upsilon") + Z_path = os.path.join(sbox.wc_dir, "A", "Z") + os.mkdir(Z_path) + +#---------------------------------------------------------------------- +# Helper function to teset notification of takeovers +# +# STDOUT is a list of stdout lines from svn co --force +# +# PATHS_TAKENOVER is a dictionary of path keys that should be reported as +# taken over ("T") in STDOUT. Only the key matters, the value is not +# used. +# +# Raises svntest.Failure if any path in PATHS_TAKENOVER is not listed as +# "T" in STDOUT or if any path is listed as "T" in STDOUT and doesn't +# exist in PATHS_TAKENOVER. +def check_takeover_notification(stdout, paths_takenover): + """Test forced co output for notification of takenover paths""" + + for line in stdout: + # Is this a notification line? + # e.g. "A some/path" or "T some/path" + if re.compile("(A|T) .*").match(line): + line = line.strip() # Strip newline + # Split line into action ("A" or "T") and path. + co_action = line.split()[0] + co_path = line.split()[1] + if sys.platform == "win32": + co_path = co_path.replace("\\", "/") + if co_action == "T" and not paths_takenover.has_key(co_path): + print "Path taken over unexpectedly" + raise svntest.Failure + if co_action == "A" and paths_takenover.has_key(co_path): + print "Expected path to be taken over" + raise svntest.Failure + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + +#---------------------------------------------------------------------- + +def checkout_with_obstructions(sbox): + """co with obstructions should fail without --force""" + + make_local_tree(sbox, False, False) + + svntest.actions.run_and_verify_svn("No error where some expected", + None, SVNAnyOutput, "co", + svntest.main.current_repo_url, + sbox.wc_dir) + +#---------------------------------------------------------------------- + +def forced_checkout_of_file_with_dir_obstructions(sbox): + """forced co fails if a dir obstructs a file""" + + make_local_tree(sbox, False, False) + + # Make the "other" working copy + other_wc = sbox.add_wc_path('other') + os.mkdir(other_wc) + os.mkdir(os.path.join(other_wc, "iota")) + + # Checkout the standard greek repos into a directory that has a dir named + # "iota" obstructing the file "iota" in the repos. This should fail. + sout, serr = svntest.actions.run_and_verify_svn("Expected error during co", + None, SVNAnyOutput, "co", + "--force", sbox.repo_url, + other_wc) + + expected_error = "svn: Failed to takeover file.*a non-file object of the same name already exists" + + if not re.compile(expected_error).search(serr[0]): + print "forced co failed but not in the expected way" + raise svntest.Failure + +#---------------------------------------------------------------------- + +def forced_checkout_of_dir_with_file_obstructions(sbox): + """forced co fails if a file obstructs a dir""" + + make_local_tree(sbox, False, False) + + # Make the "other" working copy + other_wc = sbox.add_wc_path('other') + os.mkdir(other_wc) + svntest.main.file_append(os.path.join(other_wc, "A"), "The file A") + + # Checkout the standard greek repos into a directory that has a file named + # "A" obstructing the dir "A" in the repos. This should fail. + sout, serr = svntest.actions.run_and_verify_svn("Expected error during co", + None, SVNAnyOutput, "co", + "--force", sbox.repo_url, + other_wc) + + expected_error = "svn: Failed to takeover directory.*a non-directory object of the same name already exists" + + if not re.compile(expected_error).search(serr[0]): + print "forced co failed but not in the expected way" + raise svntest.Failure + +#---------------------------------------------------------------------- + +def forced_checkout_with_faux_obstructions(sbox): + """co with faux obstructions ok with --force""" + + # Make a local tree that partially obstructs the paths coming from the + # repos but has no true differences. + make_local_tree(sbox, False, False) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"iota":"T", + "A":"T", + "A/mu":"T"}) + finally: + os.chdir(old_cwd) + + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + + # Despite the WC files taken over, there should be no local mods. + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + +#---------------------------------------------------------------------- + +def forced_checkout_with_real_obstructions(sbox): + """co with real obstructions ok with --force""" + + # Make a local tree that partially obstructs the paths coming from the + # repos and make the obstructing files different from the standard greek + # tree. + make_local_tree(sbox, True, False) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", + "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"iota":"T", + "A":"T", + "A/mu":"T"}) + finally: + os.chdir(old_cwd) + + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_output.tweak("iota", status="M ") + expected_output.tweak("A/mu", status="M ") + + # iota and A/mu should show up as modified. + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + +#---------------------------------------------------------------------- + +def forced_checkout_with_real_obstructions_and_unversioned_files(sbox): + """co with real obstructions and unversioned files""" + + # Make a local tree that partially obstructs the paths coming from the + # repos, make the obstructing files different from the standard greek + # tree, and finally add some files that don't exist in the stardard tree. + make_local_tree(sbox, True, True) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", + "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"iota":"T", + "A":"T", + "A/mu":"T"}) + finally: + os.chdir(old_cwd) + + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_output.tweak("iota", status="M ") + expected_output.tweak("A/mu", status="M ") + + # iota and A/mu should show as modified. + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + + # run_and_verify_status is always run with -q so we need to check for + # unversioned files manually. + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + svntest.actions.run_and_verify_svn(None, [ "? sigma\n" ], [], + "status", "sigma") + svntest.actions.run_and_verify_svn(None, + [ "? " + + os.path.join("A", "upsilon") + + "\n" ], + [], "status", "A/upsilon") + finally: + os.chdir(old_cwd) + +#---------------------------------------------------------------------- + +def forced_checkout_with_versioned_obstructions_same_URL(sbox): + """co with versioned obstructions from same URL""" + + # Make a greek tree working copy then delete everything but A/B/*.* + sbox.build() + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, ".svn")) + os.remove(os.path.join(sbox.wc_dir, "iota")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", ".svn")) + os.remove(os.path.join(sbox.wc_dir, "A", "mu")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "C")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "D")) + + # Make some modifications to the obstructing versioned B dir... + # ...a local file mod + svntest.main.file_write(os.path.join(sbox.wc_dir, "A", "B", "lambda"), + "This is the local version of the file 'lambda'.\n") + + # ...a scheduled deletion + svntest.actions.run_and_verify_svn("Unexpected error during svn delete", + SVNAnyOutput, [], "delete", + os.path.join(sbox.wc_dir, "A", "B", "E")) + + # ...a scheduled addition + theta_path = os.path.join(sbox.wc_dir, "A", "B", "F", "theta") + svntest.main.file_write(theta_path, + "This is the added file 'theta'.\n") + svntest.actions.run_and_verify_svn("Unexpected error during svn add", + SVNAnyOutput, [], "add", theta_path) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", + "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"A":"T", + "A/B":"T", + "A/B/lambda":"T", + "A/B/E":"T", + "A/B/E/alpha":"T", + "A/B/E/beta":"T", + "A/B/F":"T"}) + finally: + os.chdir(old_cwd) + + # The changes to A/B should still be present. + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_output.tweak("A/B/lambda", status="M ") + expected_output.tweak("A/B/E", status="D ") + expected_output.tweak("A/B/E/alpha", status="D ") + expected_output.tweak("A/B/E/beta", status="D ") + expected_output.add({"A/B/F/theta" : Item(status="A ", wc_rev=0),}) + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + +#---------------------------------------------------------------------- + +def forced_checkout_with_versioned_obstructions_different_URL(sbox): + """co with versioned obstructions from different URL""" + + # Make a greek tree working copy + sbox.build() + + # Create a second repository with the same greek tree + repo_dir = sbox.repo_dir + repo_url = sbox.repo_url + other_repo_dir, other_repo_url = sbox.add_repo_path("other") + svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 1) + + other_wc_dir = sbox.add_wc_path("other") + os.mkdir(other_wc_dir) + + # Checkout "A/" from the other repos. + svntest.actions.run_and_verify_svn("Unexpected error during co", + SVNAnyOutput, [], "co", + other_repo_url + "/A", + os.path.join(other_wc_dir, "A")) + + # Checkout the first repos into "other/A". This should fail since the + # obstructing versioned directory points to a different URL. + sout, serr = svntest.actions.run_and_verify_svn("Expected error during co", + None, SVNAnyOutput, "co", + "--force", sbox.repo_url, + other_wc_dir) + + expected_error = "svn: Failed to forcibly add directory.*a versioned directory of the same name already exists but which points to a diffent URL" + + if not re.compile(expected_error).search(serr[0]): + print "forced co failed but not in the expected way" + raise svntest.Failure + +#---------------------------------------------------------------------- + +# list all tests here, starting with None: +test_list = [ None, + checkout_with_obstructions, + forced_checkout_of_file_with_dir_obstructions, + forced_checkout_of_dir_with_file_obstructions, + forced_checkout_with_faux_obstructions, + forced_checkout_with_real_obstructions, + forced_checkout_with_real_obstructions_and_unversioned_files, + forced_checkout_with_versioned_obstructions_same_URL, + forced_checkout_with_versioned_obstructions_different_URL + ] + +if __name__ == "__main__": + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. +#!/usr/bin/env python +# +# checkout_tests.py: Testing checkout --force behavior when local +# tree already exits. +# +# Subversion is a tool for revision control. +# See http://subversion.tigris.org for more information. +# +# ==================================================================== +# Copyright (c) 2000-2006 CollabNet. All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://subversion.tigris.org/license-1.html. +# If newer versions of this license are posted there, you may use a +# newer version instead, at your option. +# +###################################################################### + +# General modules +import sys, re, os + +# Our testing module +import svntest +from svntest import wc, SVNAnyOutput + +# (abbreviation) +Item = wc.StateItem + +#---------------------------------------------------------------------- +# Helper function to set up an existing local tree that has paths which +# obstruct with the incoming WC. +# +# Build a sandbox and remove all files and directories (including the +# .svn administrative dirs) from the working copy except for 'iota', +# 'A/', and 'A/mu'. +# +# If MOD_FILES is TRUE make modifications to 'iota' and 'A/mu' that +# conflict with the standard greek tree. +# +# If ADD_UNVERSIONED is TRUE create some files and directories that don't +# exist in the standard greek tree ('sigma', 'A/upsilon', 'A/Z') +def make_local_tree(sbox, mod_files=False, add_unversioned=False): + """Make a local unversioned tree to checkout into.""" + + sbox.build() + + # Remove the WC... + svntest.main.safe_rmtree(sbox.wc_dir) + export_target = sbox.wc_dir + expected_output = svntest.main.greek_state.copy() + expected_output.wc_dir = sbox.wc_dir + expected_output.desc[""] = Item() + expected_output.tweak(contents=None, status="A ") + + # ... and export an unversioned tree to the same location. + svntest.actions.run_and_verify_export(svntest.main.current_repo_url, + export_target, + expected_output, + svntest.main.greek_state.copy()) + + # Remove everything remaining except for 'iota', 'A/', and 'A/mu'. + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "B")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "C")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "D")) + + if mod_files: + iota_path = os.path.join(sbox.wc_dir, "iota") + mu_path = os.path.join(sbox.wc_dir, "A", "mu") + svntest.main.file_write(iota_path, + "This is the local version of the file 'iota'.\n") + svntest.main.file_write(mu_path, + "This is the local version of the file 'mu'.\n") + + if add_unversioned: + sigma_path = os.path.join(sbox.wc_dir, "sigma") + svntest.main.file_append(sigma_path, "unversioned sigma") + upsilon_path = os.path.join(sbox.wc_dir, "A", "upsilon") + svntest.main.file_append(upsilon_path, "unversioned upsilon") + Z_path = os.path.join(sbox.wc_dir, "A", "Z") + os.mkdir(Z_path) + +#---------------------------------------------------------------------- +# Helper function to teset notification of takeovers +# +# STDOUT is a list of stdout lines from svn co --force +# +# PATHS_TAKENOVER is a dictionary of path keys that should be reported as +# taken over ("T") in STDOUT. Only the key matters, the value is not +# used. +# +# Raises svntest.Failure if any path in PATHS_TAKENOVER is not listed as +# "T" in STDOUT or if any path is listed as "T" in STDOUT and doesn't +# exist in PATHS_TAKENOVER. +def check_takeover_notification(stdout, paths_takenover): + """Test forced co output for notification of takenover paths""" + + for line in stdout: + # Is this a notification line? + # e.g. "A some/path" or "T some/path" + if re.compile("(A|T) .*").match(line): + line = line.strip() # Strip newline + # Split line into action ("A" or "T") and path. + co_action = line.split()[0] + co_path = line.split()[1] + if sys.platform == "win32": + co_path = co_path.replace("\\", "/") + if co_action == "T" and not paths_takenover.has_key(co_path): + print "Path taken over unexpectedly" + raise svntest.Failure + if co_action == "A" and paths_takenover.has_key(co_path): + print "Expected path to be taken over" + raise svntest.Failure + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + +#---------------------------------------------------------------------- + +def checkout_with_obstructions(sbox): + """co with obstructions should fail without --force""" + + make_local_tree(sbox, False, False) + + svntest.actions.run_and_verify_svn("No error where some expected", + None, SVNAnyOutput, "co", + svntest.main.current_repo_url, + sbox.wc_dir) + +#---------------------------------------------------------------------- + +def forced_checkout_of_file_with_dir_obstructions(sbox): + """forced co fails if a dir obstructs a file""" + + make_local_tree(sbox, False, False) + + # Make the "other" working copy + other_wc = sbox.add_wc_path('other') + os.mkdir(other_wc) + os.mkdir(os.path.join(other_wc, "iota")) + + # Checkout the standard greek repos into a directory that has a dir named + # "iota" obstructing the file "iota" in the repos. This should fail. + sout, serr = svntest.actions.run_and_verify_svn("Expected error during co", + None, SVNAnyOutput, "co", + "--force", sbox.repo_url, + other_wc) + + expected_error = "svn: Failed to takeover file.*a non-file object of the same name already exists" + + if not re.compile(expected_error).search(serr[0]): + print "forced co failed but not in the expected way" + raise svntest.Failure + +#---------------------------------------------------------------------- + +def forced_checkout_of_dir_with_file_obstructions(sbox): + """forced co fails if a file obstructs a dir""" + + make_local_tree(sbox, False, False) + + # Make the "other" working copy + other_wc = sbox.add_wc_path('other') + os.mkdir(other_wc) + svntest.main.file_append(os.path.join(other_wc, "A"), "The file A") + + # Checkout the standard greek repos into a directory that has a file named + # "A" obstructing the dir "A" in the repos. This should fail. + sout, serr = svntest.actions.run_and_verify_svn("Expected error during co", + None, SVNAnyOutput, "co", + "--force", sbox.repo_url, + other_wc) + + expected_error = "svn: Failed to takeover directory.*a non-directory object of the same name already exists" + + if not re.compile(expected_error).search(serr[0]): + print "forced co failed but not in the expected way" + raise svntest.Failure + +#---------------------------------------------------------------------- + +def forced_checkout_with_faux_obstructions(sbox): + """co with faux obstructions ok with --force""" + + # Make a local tree that partially obstructs the paths coming from the + # repos but has no true differences. + make_local_tree(sbox, False, False) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"iota":"T", + "A":"T", + "A/mu":"T"}) + finally: + os.chdir(old_cwd) + + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + + # Despite the WC files taken over, there should be no local mods. + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + +#---------------------------------------------------------------------- + +def forced_checkout_with_real_obstructions(sbox): + """co with real obstructions ok with --force""" + + # Make a local tree that partially obstructs the paths coming from the + # repos and make the obstructing files different from the standard greek + # tree. + make_local_tree(sbox, True, False) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", + "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"iota":"T", + "A":"T", + "A/mu":"T"}) + finally: + os.chdir(old_cwd) + + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_output.tweak("iota", status="M ") + expected_output.tweak("A/mu", status="M ") + + # iota and A/mu should show up as modified. + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + +#---------------------------------------------------------------------- + +def forced_checkout_with_real_obstructions_and_unversioned_nodes(sbox): + """co with real obstructions and unversioned files""" + + # Make a local tree that partially obstructs the paths coming from the + # repos, make the obstructing files different from the standard greek + # tree, and finally add some files that don't exist in the stardard tree. + make_local_tree(sbox, True, True) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", + "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"iota":"T", + "A":"T", + "A/mu":"T"}) + finally: + os.chdir(old_cwd) + + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_output.tweak("iota", status="M ") + expected_output.tweak("A/mu", status="M ") + + # iota and A/mu should show as modified. + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + + # run_and_verify_status is always run with -q so we need to check for + # unversioned files manually. + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + svntest.actions.run_and_verify_svn(None, [ "? sigma\n" ], [], + "status", "sigma") + svntest.actions.run_and_verify_svn(None, + [ "? " + + os.path.join("A", "upsilon") + + "\n" ], + [], "status", "A/upsilon") + finally: + os.chdir(old_cwd) + +#---------------------------------------------------------------------- + +def forced_checkout_with_versioned_obstructions_same_URL(sbox): + """co with versioned obstructions from same URL""" + + # Make a greek tree working copy then delete everything but A/B/*.* + sbox.build() + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, ".svn")) + os.remove(os.path.join(sbox.wc_dir, "iota")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", ".svn")) + os.remove(os.path.join(sbox.wc_dir, "A", "mu")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "C")) + svntest.main.safe_rmtree(os.path.join(sbox.wc_dir, "A", "D")) + + # Make some modifications to the obstructing versioned B dir... + # ...a local file mod + svntest.main.file_write(os.path.join(sbox.wc_dir, "A", "B", "lambda"), + "This is the local version of the file 'lambda'.\n") + + # ...a scheduled deletion + svntest.actions.run_and_verify_svn("Unexpected error during svn delete", + SVNAnyOutput, [], "delete", + os.path.join(sbox.wc_dir, "A", "B", "E")) + + # ...a scheduled addition + theta_path = os.path.join(sbox.wc_dir, "A", "B", "F", "theta") + svntest.main.file_write(theta_path, + "This is the added file 'theta'.\n") + svntest.actions.run_and_verify_svn("Unexpected error during svn add", + SVNAnyOutput, [], "add", theta_path) + + wc_dir = sbox.wc_dir + old_cwd = os.getcwd() + try: + os.chdir(wc_dir) + so, se = svntest.actions.run_and_verify_svn("Unexpected error during forced co", + SVNAnyOutput, [], "co", + "--force", + svntest.main.current_repo_url, + ".") + check_takeover_notification(so, {"A":"T", + "A/B":"T", + "A/B/lambda":"T", + "A/B/E":"T", + "A/B/E/alpha":"T", + "A/B/E/beta":"T", + "A/B/F":"T"}) + finally: + os.chdir(old_cwd) + + # The changes to A/B should still be present. + expected_output = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_output.tweak("A/B/lambda", status="M ") + expected_output.tweak("A/B/E", status="D ") + expected_output.tweak("A/B/E/alpha", status="D ") + expected_output.tweak("A/B/E/beta", status="D ") + expected_output.add({"A/B/F/theta" : Item(status="A ", wc_rev=0),}) + svntest.actions.run_and_verify_status(sbox.wc_dir, expected_output) + +#---------------------------------------------------------------------- + +def forced_checkout_with_versioned_obstructions_different_URL(sbox): + """co with versioned obstructions from different URL""" + + # Make a greek tree working copy + sbox.build() + + # Create a second repository with the same greek tree + repo_dir = sbox.repo_dir + repo_url = sbox.repo_url + other_repo_dir, other_repo_url = sbox.add_repo_path("other") + svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 1) + + other_wc_dir = sbox.add_wc_path("other") + os.mkdir(other_wc_dir) + + # Checkout "A/" from the other repos. + svntest.actions.run_and_verify_svn("Unexpected error during co", + SVNAnyOutput, [], "co", + other_repo_url + "/A", + os.path.join(other_wc_dir, "A")) + + # Checkout the first repos into "other/A". This should fail since the + # obstructing versioned directory points to a different URL. + sout, serr = svntest.actions.run_and_verify_svn("Expected error during co", + None, SVNAnyOutput, "co", + "--force", sbox.repo_url, + other_wc_dir) + + expected_error = "svn: Failed to forcibly add directory.*a versioned directory of the same name already exists but which points to a diffent URL" + + if not re.compile(expected_error).search(serr[0]): + print "forced co failed but not in the expected way" + raise svntest.Failure + +#---------------------------------------------------------------------- + +# list all tests here, starting with None: +test_list = [ None, + checkout_with_obstructions, + forced_checkout_of_file_with_dir_obstructions, + forced_checkout_of_dir_with_file_obstructions, + forced_checkout_with_faux_obstructions, + forced_checkout_with_real_obstructions, + forced_checkout_with_real_obstructions_and_unversioned_nodes, + forced_checkout_with_versioned_obstructions_same_URL, + forced_checkout_with_versioned_obstructions_different_URL + ] + +if __name__ == "__main__": + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. Property changes on: subversion\tests\cmdline\checkout_tests.py ___________________________________________________________________ Name: svn:executable + * Name: svn:eol-style + native