Index: subversion/include/svn_wc.h =================================================================== --- subversion/include/svn_wc.h (revision 22822) +++ subversion/include/svn_wc.h (working copy) @@ -1323,6 +1323,12 @@ */ const char *changelist; + /** Local copy should be kept in working copy after deletion from repository. + * Currently used only for "this dir" when it is scheduled for deletion. + * + * @since New in 1.5. */ + svn_boolean_t keep_local; + /* IMPORTANT: If you extend this structure, check svn_wc_entry_dup() to see if you need to extend that as well. */ } svn_wc_entry_t; @@ -2062,12 +2068,15 @@ * deletion will occur. @a adm_access must hold a write lock for the parent * of @a path. * - * This function immediately deletes all files, modified and unmodified, - * versioned and unversioned from the working copy. It also immediately - * deletes unversioned directories and directories that are scheduled to be - * added. Only versioned directories will remain in the working copy, - * these get deleted by the update following the commit. + * If @a keep_local is FALSE, this function immediately deletes all files, + * modified and unmodified, versioned and unversioned from the working copy. + * It also immediately deletes unversioned directories and directories that + * are scheduled to be added. Only versioned directories will remain in the + * working copy, these get deleted by the update following the commit. * + * If @a keep_local is TRUE, then all files and directories will be kept + * in working copy. + * * If @a cancel_func is non-null, call it with @a cancel_baton at * various points during the operation. If it returns an error * (typically @c SVN_ERR_CANCELLED), return that error immediately. @@ -2076,8 +2085,22 @@ * the @a notify_baton and that path. The @a notify_func callback may be * @c NULL if notification is not needed. * - * @since New in 1.2. + * @since New in 1.5. */ +svn_error_t *svn_wc_delete3(const char *path, + svn_wc_adm_access_t *adm_access, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_boolean_t keep_local, + apr_pool_t *pool); + +/** + * Similar to svn_wc_delete3(), but with @a keep_local always set to false. + * + * @deprecated Provided for backward compatibility with the 1.4 API. + */ svn_error_t *svn_wc_delete2(const char *path, svn_wc_adm_access_t *adm_access, svn_cancel_func_t cancel_func, Index: subversion/include/svn_client.h =================================================================== --- subversion/include/svn_client.h (revision 22822) +++ subversion/include/svn_client.h (working copy) @@ -1142,6 +1142,9 @@ * modified and/or unversioned items. If @a force is set such items * will be deleted. * + * If @keep_local is TRUE then local copy of path will be kept in + * working copy. + * * @a ctx->log_msg_func3/@a ctx->log_msg_baton3 are a callback/baton * combo that this function can use to query for a commit log message * when one is needed. @@ -1150,16 +1153,29 @@ * @a ctx->notify_func2 with @a ctx->notify_baton2 and the path of the deleted * item. * - * @since New in 1.3. + * @since New in 1.5. */ svn_error_t * +svn_client_delete3(svn_commit_info_t **commit_info_p, + const apr_array_header_t *paths, + svn_boolean_t force, + svn_boolean_t keep_local, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + +/** + * Similar to svn_client_delete3(), but with @a keep_local always set + * to false + * + * @deprecated Provided for backward compatibility with the 1.4 API. + */ +svn_error_t * svn_client_delete2(svn_commit_info_t **commit_info_p, const apr_array_header_t *paths, svn_boolean_t force, svn_client_ctx_t *ctx, apr_pool_t *pool); - /** * Similar to svn_client_delete2(), but takes the @c svn_client_commit_info_t * type for @a commit_info_p. Index: subversion/libsvn_wc/entries.c =================================================================== --- subversion/libsvn_wc/entries.c (revision 22822) +++ subversion/libsvn_wc/entries.c (working copy) @@ -449,7 +449,12 @@ /* Changelist. */ SVN_ERR(read_str(&entry->changelist, buf, end, pool)); + MAYBE_DONE; + /* Keep entry in working copy after deletion? */ + SVN_ERR(read_bool(&entry->keep_local, SVN_WC__ENTRY_ATTR_KEEP_LOCAL, + buf, end)); + done: *new_entry = entry; return SVN_NO_ERROR; @@ -646,6 +651,10 @@ modify_flags, SVN_WC__ENTRY_MODIFY_INCOMPLETE, atts, SVN_WC__ENTRY_ATTR_INCOMPLETE, name)); + /* Is this item should be kept in working copy after deletion? */ + SVN_ERR(do_bool_attr(&entry->keep_local, + modify_flags, SVN_WC__ENTRY_MODIFY_KEEP_LOCAL, + atts, SVN_WC__ENTRY_ATTR_KEEP_LOCAL, name)); /* Attempt to set up timestamps. */ { @@ -1542,6 +1551,9 @@ /* Changelist. */ write_str(buf, entry->changelist, pool); + /* Keep in working copy flag. */ + write_bool(buf, SVN_WC__ENTRY_ATTR_KEEP_LOCAL, entry->keep_local); + /* Remove redundant separators at the end of the entry. */ while (buf->len > 1 && buf->data[buf->len - 2] == '\n') buf->len--; @@ -1669,6 +1681,10 @@ apr_hash_set(atts, SVN_WC__ENTRY_ATTR_INCOMPLETE, APR_HASH_KEY_STRING, (entry->incomplete ? "true" : NULL)); + /* Keep in wc flag */ + apr_hash_set(atts, SVN_WC__ENTRY_ATTR_KEEP_LOCAL, APR_HASH_KEY_STRING, + (entry->keep_local ? "true" : NULL)); + /* Timestamps */ if (entry->text_time) { @@ -2147,6 +2163,9 @@ ? apr_pstrdup(pool, entry->present_props) : NULL); + if (modify_flags & SVN_WC__ENTRY_MODIFY_KEEP_LOCAL) + cur_entry->keep_local = entry->keep_local; + /* Absorb defaults from the parent dir, if any, unless this is a subdir entry. */ if (cur_entry->kind != svn_node_dir) @@ -2178,6 +2197,13 @@ cur_entry->copyfrom_url = NULL; } + /* keep_local makes sense only when we are going to delete directory. */ + if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE + && entry->schedule != svn_wc_schedule_delete) + { + cur_entry->keep_local = FALSE; + } + /* Make sure the entry exists in the entries hash. Possibly it already did, in which case this could have been skipped, but what the heck. */ Index: subversion/libsvn_wc/entries.h =================================================================== --- subversion/libsvn_wc/entries.h (revision 22822) +++ subversion/libsvn_wc/entries.h (working copy) @@ -72,6 +72,7 @@ #define SVN_WC__ENTRY_ATTR_CACHABLE_PROPS "cachable-props" #define SVN_WC__ENTRY_ATTR_PRESENT_PROPS "present-props" #define SVN_WC__ENTRY_ATTR_CHANGELIST "changelist" +#define SVN_WC__ENTRY_ATTR_KEEP_LOCAL "keep-local" /* Attribute values for 'schedule' */ #define SVN_WC__ENTRY_VALUE_ADD "add" @@ -149,14 +150,15 @@ #define SVN_WC__ENTRY_MODIFY_CACHABLE_PROPS APR_INT64_C(0x10000000) #define SVN_WC__ENTRY_MODIFY_PRESENT_PROPS APR_INT64_C(0x20000000) #define SVN_WC__ENTRY_MODIFY_CHANGELIST APR_INT64_C(0x40000000) +#define SVN_WC__ENTRY_MODIFY_KEEP_LOCAL APR_INT64_C(0x80000000) /* ...or perhaps this to mean all of those above... */ -#define SVN_WC__ENTRY_MODIFY_ALL APR_INT64_C(0x7FFFFFFF) +#define SVN_WC__ENTRY_MODIFY_ALL APR_INT64_C(0xFFFFFFFF) /* ...ORed together with this to mean "I really mean this, don't be trying to protect me from myself on this one." */ -#define SVN_WC__ENTRY_MODIFY_FORCE APR_INT64_C(0x80000000) +#define SVN_WC__ENTRY_MODIFY_FORCE APR_INT64_C(0x100000000) /* Modify an entry for NAME in access baton ADM_ACCESS by folding in Index: subversion/libsvn_wc/wc.h =================================================================== --- subversion/libsvn_wc/wc.h (revision 22822) +++ subversion/libsvn_wc/wc.h (working copy) @@ -138,6 +138,9 @@ directory. */ #define SVN_WC__THIS_DIR_PREJ "dir_conflicts" +/* The contents of killme file, which signals to delete administrative + area only. */ +#define SVN_WC__KILL_ADM_ONLY "adm-only" /* A space separated list of properties that we cache presence/absence of. Index: subversion/libsvn_wc/log.c =================================================================== --- subversion/libsvn_wc/log.c (revision 22822) +++ subversion/libsvn_wc/log.c (working copy) @@ -1067,9 +1067,9 @@ loggy->entries_modified = TRUE; /* Drop the 'killme' file. */ - err = svn_wc__make_adm_thing(loggy->adm_access, SVN_WC__ADM_KILLME, - svn_node_file, APR_OS_DEFAULT, - 0, pool); + err = svn_wc__make_killme(loggy->adm_access, entry->keep_local, + pool); + if (err) { if (loggy->rerun && APR_STATUS_IS_EEXIST(err->apr_err)) @@ -1077,8 +1077,8 @@ else return err; } + return SVN_NO_ERROR; - } /* Else, we're deleting a file, and we can safely remove files @@ -1642,6 +1642,7 @@ */ static svn_error_t * handle_killme(svn_wc_adm_access_t *adm_access, + svn_boolean_t adm_only, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) @@ -1656,7 +1657,7 @@ /* Blow away the entire directory, and all those below it too. */ err = svn_wc_remove_from_revision_control(adm_access, SVN_WC_ENTRY_THIS_DIR, - TRUE, /* destroy */ + !adm_only, /* destroy */ FALSE, /* no instant err */ cancel_func, cancel_baton, pool); @@ -1765,6 +1766,7 @@ const char *logfile_path; int log_number; apr_pool_t *iterpool = svn_pool_create(pool); + svn_boolean_t killme, kill_adm_only; /* kff todo: use the tag-making functions here, now. */ const char *log_start @@ -1857,10 +1859,10 @@ SVN_ERR(svn_wc__wcprops_write(loggy->adm_access, pool)); /* Check for a 'killme' file in the administrative area. */ - if (svn_wc__adm_path_exists(svn_wc_adm_access_path(adm_access), 0, pool, - SVN_WC__ADM_KILLME, NULL)) + SVN_ERR(svn_wc__check_killme(adm_access, &killme, &kill_adm_only, pool)); + if (killme) { - SVN_ERR(handle_killme(adm_access, NULL, NULL, pool)); + SVN_ERR(handle_killme(adm_access, kill_adm_only, NULL, NULL, pool)); } else { @@ -2470,6 +2472,7 @@ svn_boolean_t cleanup; int wc_format_version; apr_pool_t *subpool; + svn_boolean_t killme, kill_adm_only; /* Check cancellation; note that this catches recursive calls too. */ if (cancel_func) @@ -2527,11 +2530,13 @@ } svn_pool_destroy(subpool); - if (svn_wc__adm_path_exists(svn_wc_adm_access_path(adm_access), 0, pool, - SVN_WC__ADM_KILLME, NULL)) + SVN_ERR(svn_wc__check_killme(adm_access, &killme, &kill_adm_only, pool)); + + if (killme) { /* A KILLME indicates that the log has already been run */ - SVN_ERR(handle_killme(adm_access, cancel_func, cancel_baton, pool)); + SVN_ERR(handle_killme(adm_access, kill_adm_only, cancel_func, + cancel_baton, pool)); } else { Index: subversion/libsvn_wc/adm_ops.c =================================================================== --- subversion/libsvn_wc/adm_ops.c (revision 22822) +++ subversion/libsvn_wc/adm_ops.c (working copy) @@ -841,13 +841,14 @@ } -/* Recursively mark a tree ADM_ACCESS with a SCHEDULE and/or COPIED +/* Recursively mark a tree ADM_ACCESS with a SCHEDULE, COPIED and/or KEEP_LOCAL flag, depending on the state of MODIFY_FLAGS. */ static svn_error_t * mark_tree(svn_wc_adm_access_t *adm_access, apr_uint64_t modify_flags, svn_wc_schedule_t schedule, svn_boolean_t copied, + svn_boolean_t keep_local, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, @@ -858,6 +859,8 @@ apr_hash_t *entries; apr_hash_index_t *hi; const svn_wc_entry_t *entry; + svn_wc_entry_t tmp_entry; + apr_uint64_t this_dir_flags; /* Read the entries file for this directory. */ SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool)); @@ -869,7 +872,6 @@ const void *key; void *val; const char *base_name; - svn_wc_entry_t *dup_entry; /* Clear our per-iteration pool. */ svn_pool_clear(subpool); @@ -893,18 +895,19 @@ SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, fullpath, subpool)); SVN_ERR(mark_tree(child_access, modify_flags, - schedule, copied, + schedule, copied, keep_local, cancel_func, cancel_baton, notify_func, notify_baton, subpool)); } - /* Need to duplicate the entry because we are changing the scheduling */ - dup_entry = svn_wc_entry_dup(entry, subpool); - dup_entry->schedule = schedule; - dup_entry->copied = copied; - SVN_ERR(svn_wc__entry_modify(adm_access, base_name, dup_entry, - modify_flags, TRUE, subpool)); + tmp_entry.schedule = schedule; + tmp_entry.copied = copied; + SVN_ERR(svn_wc__entry_modify + (adm_access, base_name, &tmp_entry, + modify_flags & (SVN_WC__ENTRY_MODIFY_SCHEDULE + | SVN_WC__ENTRY_MODIFY_COPIED), + TRUE, subpool)); /* Tell someone what we've done. */ if (schedule == svn_wc_schedule_delete && notify_func != NULL) @@ -915,23 +918,39 @@ /* Handle "this dir" for states that need it done post-recursion. */ entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING); - + this_dir_flags = 0; + /* Uncommitted directories (schedule add) that are to be scheduled for deletion are a special case, they don't need to be changed as they will be removed from their parent's entry list. */ if (! (entry->schedule == svn_wc_schedule_add && schedule == svn_wc_schedule_delete)) { - /* Need to duplicate the entry because we are changing the scheduling */ - svn_wc_entry_t *dup_entry = svn_wc_entry_dup(entry, subpool); if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE) - dup_entry->schedule = schedule; + { + tmp_entry.schedule = schedule; + this_dir_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE; + } + if (modify_flags & SVN_WC__ENTRY_MODIFY_COPIED) - dup_entry->copied = copied; - SVN_ERR(svn_wc__entry_modify(adm_access, NULL, dup_entry, modify_flags, + { + tmp_entry.copied = copied; + this_dir_flags |= SVN_WC__ENTRY_MODIFY_COPIED; + } + } + + /* Set keep_local on the "this dir", if requested. */ + if (modify_flags & SVN_WC__ENTRY_MODIFY_KEEP_LOCAL) + { + tmp_entry.keep_local = keep_local; + this_dir_flags |= SVN_WC__ENTRY_MODIFY_KEEP_LOCAL; + } + + /* Modify this_dir entry if requested. */ + if (this_dir_flags) + SVN_ERR(svn_wc__entry_modify(adm_access, NULL, &tmp_entry, this_dir_flags, TRUE, subpool)); - } - + /* Destroy our per-iteration pool. */ svn_pool_destroy(subpool); return SVN_NO_ERROR; @@ -1095,12 +1114,13 @@ svn_error_t * -svn_wc_delete2(const char *path, +svn_wc_delete3(const char *path, svn_wc_adm_access_t *adm_access, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, + svn_boolean_t keep_local, apr_pool_t *pool) { svn_wc_adm_access_t *dir_access; @@ -1175,8 +1195,10 @@ if (dir_access != adm_access) { /* Recursively mark a whole tree for deletion. */ - SVN_ERR(mark_tree(dir_access, SVN_WC__ENTRY_MODIFY_SCHEDULE, - svn_wc_schedule_delete, FALSE, + SVN_ERR(mark_tree(dir_access, + SVN_WC__ENTRY_MODIFY_SCHEDULE + | SVN_WC__ENTRY_MODIFY_KEEP_LOCAL, + svn_wc_schedule_delete, FALSE, keep_local, cancel_func, cancel_baton, notify_func, notify_baton, pool)); @@ -1251,17 +1273,33 @@ /* By the time we get here, anything that was scheduled to be added has become unversioned */ - if (was_schedule == svn_wc_schedule_add) - SVN_ERR(erase_unversioned_from_wc - (path, cancel_func, cancel_baton, pool)); - else - SVN_ERR(erase_from_wc(path, adm_access, was_kind, - cancel_func, cancel_baton, pool)); + if (!keep_local) + { + if (was_schedule == svn_wc_schedule_add) + SVN_ERR(erase_unversioned_from_wc + (path, cancel_func, cancel_baton, pool)); + else + SVN_ERR(erase_from_wc(path, adm_access, was_kind, + cancel_func, cancel_baton, pool)); + } return SVN_NO_ERROR; } svn_error_t * +svn_wc_delete2(const char *path, + svn_wc_adm_access_t *adm_access, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + return svn_wc_delete3(path, adm_access, cancel_func, cancel_baton, + notify_func, notify_baton, FALSE, pool); +} + +svn_error_t * svn_wc_delete(const char *path, svn_wc_adm_access_t *adm_access, svn_cancel_func_t cancel_func, @@ -1529,7 +1567,7 @@ /* Recursively add the 'copied' existence flag as well! */ SVN_ERR(mark_tree(adm_access, SVN_WC__ENTRY_MODIFY_COPIED, - svn_wc_schedule_normal, TRUE, + svn_wc_schedule_normal, TRUE, FALSE, cancel_func, cancel_baton, NULL, NULL, /* N/A cuz we aren't deleting */ Index: subversion/libsvn_wc/adm_files.c =================================================================== --- subversion/libsvn_wc/adm_files.c (revision 22822) +++ subversion/libsvn_wc/adm_files.c (working copy) @@ -264,6 +264,62 @@ } +/* Create an killme file in the adm area. + * + * If ADM_ONLY is non-zero then only administrative area will be removed, + * otherwise all directory. + */ +svn_error_t * +svn_wc__make_killme(svn_wc_adm_access_t *adm_access, + svn_boolean_t adm_only, + apr_pool_t *pool) +{ + const char *path; + + SVN_ERR(svn_wc__adm_write_check(adm_access)); + + path = extend_with_adm_name(svn_wc_adm_access_path(adm_access), + NULL, FALSE, pool, SVN_WC__ADM_KILLME, NULL); + + return svn_io_file_create(path, adm_only ? SVN_WC__KILL_ADM_ONLY : "", pool); +} + +svn_error_t * +svn_wc__check_killme(svn_wc_adm_access_t *adm_access, + svn_boolean_t *exists, + svn_boolean_t *kill_adm_only, + apr_pool_t *pool) +{ + const char *path; + svn_error_t *err; + svn_stringbuf_t *contents; + + path = extend_with_adm_name(svn_wc_adm_access_path(adm_access), + NULL, FALSE, pool, SVN_WC__ADM_KILLME, NULL); + + /* By default think that killme file exists. */ + *exists = TRUE; + err = svn_stringbuf_from_file(&contents, path, pool); + + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + /* Killme file doesn't exist. */ + *exists = FALSE; + svn_error_clear(err); + } + else if (err) + { + return err; + } + + /* If killme file contains string 'adm-only' then remove only administrative + area. */ + *kill_adm_only = svn_string_compare_stringbuf + (svn_string_create(SVN_WC__KILL_ADM_ONLY, pool), contents); + + return SVN_NO_ERROR; +} + /*** Syncing files in the adm area. ***/ Index: subversion/libsvn_wc/adm_files.h =================================================================== --- subversion/libsvn_wc/adm_files.h (revision 22822) +++ subversion/libsvn_wc/adm_files.h (working copy) @@ -58,6 +58,21 @@ svn_boolean_t tmp, apr_pool_t *pool); +/* Make 'PATH//killme' file. + If adm_only is non-zero then file will contain text 'adm-only', + otherwise file will be empty. */ +svn_error_t *svn_wc__make_killme(svn_wc_adm_access_t *adm_access, + svn_boolean_t adm_only, + apr_pool_t *pool); + +/* Sets EXISTS to TRUE if killme file exists in the administrative area, FALSE + otherwise. + Also sets KILL_ADM_ONLY to value passed to svn_wc__make_killme() */ +svn_error_t *svn_wc__check_killme(svn_wc_adm_access_t *adm_access, + svn_boolean_t *exists, + svn_boolean_t *kill_adm_only, + apr_pool_t *pool); + /* Atomically rename a temporary text-base file to its canonical location. The tmp file should be closed already. */ svn_error_t * Index: subversion/libsvn_client/delete.c =================================================================== --- subversion/libsvn_client/delete.c (revision 22822) +++ subversion/libsvn_client/delete.c (working copy) @@ -213,30 +213,32 @@ svn_client__wc_delete(const char *path, svn_wc_adm_access_t *adm_access, svn_boolean_t force, - svn_boolean_t dry_run, + svn_boolean_t dry_run, + svn_boolean_t keep_local, svn_wc_notify_func2_t notify_func, void *notify_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { - if (!force) + if (!force && !keep_local) /* Verify that there are no "awkward" files */ SVN_ERR(svn_client__can_delete(path, ctx, pool)); if (!dry_run) /* Mark the entry for commit deletion and perform wc deletion */ - SVN_ERR(svn_wc_delete2(path, adm_access, + SVN_ERR(svn_wc_delete3(path, adm_access, ctx->cancel_func, ctx->cancel_baton, - notify_func, notify_baton, pool)); + notify_func, notify_baton, keep_local, pool)); return SVN_NO_ERROR; } svn_error_t * -svn_client_delete2(svn_commit_info_t **commit_info_p, +svn_client_delete3(svn_commit_info_t **commit_info_p, const apr_array_header_t *paths, - svn_boolean_t force, + svn_boolean_t force, + svn_boolean_t keep_local, svn_client_ctx_t *ctx, apr_pool_t *pool) { @@ -270,7 +272,7 @@ TRUE, 0, ctx->cancel_func, ctx->cancel_baton, subpool)); SVN_ERR(svn_client__wc_delete(path, adm_access, force, - FALSE, + FALSE, keep_local, ctx->notify_func2, ctx->notify_baton2, ctx, subpool)); @@ -282,6 +284,15 @@ return SVN_NO_ERROR; } +svn_error_t * +svn_client_delete2(svn_commit_info_t **commit_info_p, + const apr_array_header_t *paths, + svn_boolean_t force, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + return svn_client_delete3(commit_info_p, paths, force, FALSE, ctx, pool); +} svn_error_t * svn_client_delete(svn_client_commit_info_t **commit_info_p, Index: subversion/libsvn_client/client.h =================================================================== --- subversion/libsvn_client/client.h (revision 22822) +++ subversion/libsvn_client/client.h (working copy) @@ -308,7 +308,10 @@ /* The main logic for client deletion from a working copy. Deletes PATH from ADM_ACCESS. If PATH (or any item below a directory PATH) is - modified the delete will fail and return an error unless FORCE is TRUE. + modified the delete will fail and return an error unless FORCE or KEEP_LOCAL + is TRUE. + If KEEP_LOCAL is TRUE then path scheduled to delete from repository only + and local copy of path will be kept in working copy. If DRY_RUN is TRUE all the checks are made to ensure that the delete can occur, but the working copy is not modified. If NOTIFY_FUNC is not null, it is called with NOTIFY_BATON for each file or directory deleted. */ @@ -316,6 +319,7 @@ svn_wc_adm_access_t *adm_access, svn_boolean_t force, svn_boolean_t dry_run, + svn_boolean_t keep_local, svn_wc_notify_func2_t notify_func, void *notify_baton, svn_client_ctx_t *ctx, Index: subversion/libsvn_client/diff.c =================================================================== --- subversion/libsvn_client/diff.c (revision 22822) +++ subversion/libsvn_client/diff.c (working copy) @@ -1127,7 +1127,7 @@ /* Passing NULL for the notify_func and notify_baton because repos_diff.c:delete_entry() will do it for us. */ err = svn_client__wc_delete(mine, parent_access, merge_b->force, - merge_b->dry_run, NULL, NULL, + merge_b->dry_run, FALSE, NULL, NULL, merge_b->ctx, subpool); if (err && state) { @@ -1356,7 +1356,7 @@ SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent_path, subpool)); err = svn_client__wc_delete(path, parent_access, merge_b->force, - merge_b->dry_run, + merge_b->dry_run, FALSE, merge_delete_notify_func, &mdb, merge_b->ctx, subpool); if (err && state) Index: subversion/tests/cmdline/basic_tests.py =================================================================== --- subversion/tests/cmdline/basic_tests.py (revision 22822) +++ subversion/tests/cmdline/basic_tests.py (working copy) @@ -1706,6 +1706,52 @@ '--password', svntest.main.wc_passwd, sbox.repo_url) + +def delete_keep_local(sbox): + 'delete file and directory with --keep-local' + + sbox.build() + wc_dir = sbox.wc_dir + iota_path = os.path.join(wc_dir, 'iota') + C_path = os.path.join(wc_dir, 'A', 'C') + + # Remove file iota + svntest.actions.run_and_verify_svn(None, None, [], 'rm', '--keep-local', + iota_path) + + # Remove directory 'A/C' + svntest.actions.run_and_verify_svn(None, None, [], 'rm', '--keep-local', + C_path) + + # Commit changes + expected_output = wc.State(wc_dir, { + 'iota' : Item(verb='Deleting'), + 'A/C' : Item(verb='Deleting'), + }) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.remove('iota') + expected_status.remove('A/C') + + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + wc_dir) + + # Update working copy to check disk state still greek tree + expected_disk = svntest.main.greek_state.copy() + expected_output = svntest.wc.State(wc_dir, {}) + expected_status.tweak(wc_rev = 2); + + svntest.actions.run_and_verify_update(wc_dir, + expected_output, + expected_disk, + expected_status) + + ######################################################################## # Run the tests @@ -1742,6 +1788,7 @@ ls_nonhead, cat_added_PREV, ls_space_in_repo_name, + delete_keep_local, ### todo: more tests needed: ### test "svn rm http://some_url" ### not sure this file is the right place, though. Index: subversion/svn/cl.h =================================================================== --- subversion/svn/cl.h (revision 22822) +++ subversion/svn/cl.h (working copy) @@ -80,7 +80,8 @@ svn_cl__summarize, svn_cl__targets_opt, svn_cl__version_opt, - svn_cl__xml_opt + svn_cl__xml_opt, + svn_cl__keep_local_opt } svn_cl__longopt_t; @@ -149,6 +150,7 @@ svn_boolean_t clear; /* deassociate a changelist */ const char *changelist; /* operate on this changelist */ svn_boolean_t keep_changelist; /* don't remove changelist after commit */ + svn_boolean_t keep_local; /* delete path only from repository */ } svn_cl__opt_state_t; Index: subversion/svn/delete-cmd.c =================================================================== --- subversion/svn/delete-cmd.c (revision 22822) +++ subversion/svn/delete-cmd.c (working copy) @@ -70,7 +70,8 @@ NULL, ctx->config, pool)); } - err = svn_client_delete2(&commit_info, targets, opt_state->force, ctx, pool); + err = svn_client_delete3(&commit_info, targets, opt_state->force, + opt_state->keep_local, ctx, pool); if (err) err = svn_cl__may_need_force(err); Index: subversion/svn/main.c =================================================================== --- subversion/svn/main.c (revision 22822) +++ subversion/svn/main.c (working copy) @@ -190,6 +190,8 @@ N_("operate only on members of changelist ARG")}, {"keep-changelist", svn_cl__keep_changelist_opt, 0, N_("don't delete changelist after commit")}, + {"keep-local", svn_cl__keep_local_opt, 0, + N_("keep path in working copy")}, {0, 0, 0, 0} }; @@ -337,14 +339,16 @@ "\n" " 1. Each item specified by a PATH is scheduled for deletion upon\n" " the next commit. Files, and directories that have not been\n" - " committed, are immediately removed from the working copy.\n" + " committed, are immediately removed from the working copy\n" + " unless --keep-local option is given.\n" " PATHs that are, or contain, unversioned or modified items will\n" " not be removed unless the --force option is given.\n" "\n" " 2. Each item specified by a URL is deleted from the repository\n" " via an immediate commit.\n"), {svn_cl__force_opt, 'q', svn_cl__targets_opt, - SVN_CL__LOG_MSG_OPTIONS, SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt} }, + SVN_CL__LOG_MSG_OPTIONS, SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt, + svn_cl__keep_local_opt} }, { "diff", svn_cl__diff, {"di"}, N_ ("Display the differences between two revisions or paths.\n" @@ -1264,6 +1268,9 @@ case svn_cl__keep_changelist_opt: opt_state.keep_changelist = TRUE; break; + case svn_cl__keep_local_opt: + opt_state.keep_local = TRUE; + break; default: /* Hmmm. Perhaps this would be a good place to squirrel away opts that commands like svn diff might need. Hmmm indeed. */