Log message: [[[ Allow case-only renames on case-insensitive filesystems (issue #3702). * subversion/svn/move-cmd.c (svn_cl__move): If the targets are paths, and there are exactly two, and the normalized DST_PATH and SRC_PATH are equal, canonicalize the original DST_PATH again, but without converting it to on-disk casing. * subversion/libsvn_client/copy.c (do_wc_to_wc_moves_with_locks2): Pass TRUE for keep_local when calling svn_wc_delete4 after just having performed the svn_io_file_rename. Because it has just been renamed, the src_abspath_or_url should already be gone. This avoids deleting the destination in case of a case-only rename. (verify_wc_srcs_and_dsts): When the dst_abspath_or_url already exists, see if its "truepath" (matching path on the filesystem, module case) is the same as the src_abspath_or_url, to account for a possible case-only rename. In that case, don't complain about that the dst already exists. ]]] Index: subversion/svn/move-cmd.c =================================================================== --- subversion/svn/move-cmd.c (revision 1092816) +++ subversion/svn/move-cmd.c (working copy) @@ -30,6 +30,7 @@ #include "svn_client.h" #include "svn_error.h" #include "svn_path.h" +#include "svn_utf.h" #include "cl.h" #include "svn_private_config.h" @@ -76,6 +77,27 @@ svn_cl__move(apr_getopt_t *os, (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL, _("Local, non-commit operations do not take a log message " "or revision properties")); + + /* For allowing case-only renames on Windows (issue #3702): + If DST_PATH is a path, and there is exactly one source path, check if + the normalized DST_PATH and SRC_PATH are equal. If so, canonicalize + the original DST_PATH again, but without converting it to on-disk + casing, so we can pass the intended destination to + svn_client_move6. */ + if (targets->nelts == 1) + { + const char *src_path = APR_ARRAY_IDX(targets, targets->nelts - 1, + const char *); + + if (strcmp(src_path, dst_path) == 0) + { + const char *utf8_target; + + SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target, + os->argv[os->argc - 1], pool)); + dst_path = svn_dirent_canonicalize(utf8_target, pool); + } + } } if (ctx->log_msg_func3) Index: subversion/libsvn_client/copy.c =================================================================== --- subversion/libsvn_client/copy.c (revision 1092816) +++ subversion/libsvn_client/copy.c (working copy) @@ -347,8 +347,14 @@ do_wc_to_wc_moves_with_locks2(void *baton, SVN_ERR(svn_io_file_rename(b->pair->src_abspath_or_url, dst_abspath, scratch_pool)); + /* ### JC: Deleting with keep_local==TRUE should be fine, because the above + rename should have already (re)moved src_abspath_or_url, so it should no + longer be present on the filesystem. Without keep_local, a case-only + rename on a case-insensitive filesystem doesn't work: the dst of the + rename is deleted here (because it's equal to the src, modulo case). */ SVN_ERR(svn_wc_delete4(b->ctx->wc_ctx, b->pair->src_abspath_or_url, - FALSE, FALSE, + TRUE /* keep_local */, + FALSE /* delete_unversioned_target */, b->ctx->cancel_func, b->ctx->cancel_baton, b->ctx->notify_func2, b->ctx->notify_baton2, scratch_pool)); @@ -482,11 +488,46 @@ verify_wc_srcs_and_dsts(const apr_array_header_t * SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, iterpool)); if (dst_kind != svn_node_none) - return svn_error_createf( - SVN_ERR_ENTRY_EXISTS, NULL, - _("Path '%s' already exists"), - svn_dirent_local_style(pair->dst_abspath_or_url, pool)); + { + /* ### JC: Get the dst's truepath, just in case we passed a + non-truepath dst for doing a case-only rename, so we can check + here if we're doing a case-only rename (which shouldn't be + blocked here). This code is mainly copy-pasted from + libsvn_subr/opt.c#svn_opt__arg_canonicalize_path - should + probably end up somewhere else ... */ + const char *apr_target; + const char *dst_truepath; + const char *dst_truepath_utf8; + apr_status_t apr_err; + + SVN_ERR(svn_path_cstring_from_utf8(&apr_target, + pair->dst_abspath_or_url, + iterpool)); + apr_err = apr_filepath_merge(&dst_truepath, "", apr_target, + APR_FILEPATH_TRUENAME, iterpool); + /* convert back to UTF-8. */ + SVN_ERR(svn_path_cstring_to_utf8(&dst_truepath_utf8, + dst_truepath, iterpool)); + dst_truepath_utf8 = svn_dirent_canonicalize(dst_truepath_utf8, + iterpool); + if (dst_kind == pair->src_kind + && strcmp(pair->src_abspath_or_url, + dst_truepath_utf8) == 0 + && strcmp(pair->src_abspath_or_url, + pair->dst_abspath_or_url) != 0) + { + /* It's a case-only rename, just let it go through */ + } + else + { + return svn_error_createf( + SVN_ERR_ENTRY_EXISTS, NULL, + _("Path '%s' already exists"), + svn_dirent_local_style(pair->dst_abspath_or_url, pool)); + } + } + svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, pair->dst_abspath_or_url, pool);