On Sat, Apr 16, 2011 at 10:17 AM, Johan Corveleyn <[email protected]> wrote:
> Hi,
>
> Following discussion in [1], I tried to write a patch for issue #3702
> ("svn ren TODO todo" not work on windows). As mentioned by Bert in
> comment to the issue [2], we need to avoid letting 'svn move' convert
> the destination path to on-disk casing in this case, so that's what
> the below patch does.
>
> However, it seems that's not enough. I'm getting further, but now the
> move is blocked libsvn_client/copy.c. The error I'm getting now is:
>
> svn: E155007: Path 'C:\Temp\test\todo' is not a directory
>
> The problem is that, in copy.c#svn_client_move6, it first tries the
> move, but if the dst_path already exists (which comes down to an
> apr_stat inside copy.c#verify_wc_srcs_and_dsts), it tries the move
> again with moving the src_path to a child of (the presumed directory)
> dst_path:
>
> libsvn_client/copy.c: 2313
> [[[
> err = try_copy(sources, dst_path,
> TRUE /* is_move */,
> make_parents,
> FALSE,
> revprop_table,
> commit_callback, commit_baton,
> ctx,
> subpool);
>
> /* If the destination exists, try to move the sources as children of the
> destination. */
> if (move_as_child && err && (src_paths->nelts == 1)
> && (err->apr_err == SVN_ERR_ENTRY_EXISTS
> || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
> {
> ...
> err = try_copy(sources,
> dst_is_url
> ? svn_path_url_add_component2(dst_path,
> src_basename, pool)
> : svn_dirent_join(dst_path, src_basename, pool),
> ...
> ]]]
>
> So, a fix in the client layer is needed as well. Any suggestions on
> how to best approach this?
>
> Right now, I'm thinking the fix should be in verify_wc_srcs_and_dsts
> (similar to the "special case" of my below patch for move-cmd.c):
> while checking each copy_pair, if the dst_path already exists, check
> the "true path" of dst_path (APR_FILEPATH_TRUENAME), and see if it's
> the same as the src_path. If so, don't return an already-exists-error.
> But I'm not sure if that's correct, may have side-effects, ...
In attachment is a second attempted patch that implements the above
suggestion: in copy.c, when verifying srcs_and_dsts, if a dst clashes
with an existing file, check if its truepath is the same as the src
(if so, let it go through). This uses some copy-pasted code from
opt.c, so maybe some refactoring is needed here ... Or another way to
pass both the truepath and the originally-requested-path.
After that the "svn move" went through, but the moved file was deleted
from the file system. I fixed that by setting keep_local to TRUE in
the call to svn_wc_delete4 in do_wc_to_wc_moves_with_locks2. At first
sight this seems ok to me, but I'm very much in unfamiliar territory,
so I'm not sure at all :-). See the comment in the patch for some
additional explanation.
Comments, review, ... very much appreciated.
With this patch, one can perform case-only renames on Windows.
BUT it raises some additional questions/issues:
- How to commit such a move? Committing the parent directory
recursively works fine, but if you try to specify only the move
targets (src and dst paths), commit runs into the same problem as what
I was trying to address here: both paths are converted to their
"truepath", which means only the added side is seen by commit (the
only part that's still on the filesystem):
C:\Temp\test4>svn mv foo2 Foo2
A Foo2
D foo2
C:\Temp\test4>svn commit -mtest foo2 Foo2
Adding Foo2
Committed revision 96322.
This is very undesirable, because after this commit this cannot be
checked out or updated anymore on a Windows client (case-clashing
files).
- In fact: the same problem holds true for other commands as well: how
to revert both sides of this move? Ok, one can revert in two steps ...
- Maybe a more general solution is needed, so all commands can
adequately see which path the user actually means? The "truepath"
corresponding to a given path (modulo case), or really the path in the
case given by the user? A shot in the dark: (1) first look in the
wc-db - if the path matches a path in the wc-db, accept it as is, else
(2) convert it to its truepath (path on the filesystem that matches
modulo case). Except for "svn move", as implemented in this patch ...
- Note that the above problem is already present now on trunk (without
my patch): since we can now represent case-clashing paths in the WC
even on a case-insensitive filesystem. (See Bert's example in [2],
"svn ren TODO todoQ; svn ren todoQ todo").
Cheers,
--
Johan
> [1] http://svn.haxx.se/dev/archive-2011-04/0232.shtml
> [2] http://subversion.tigris.org/issues/show_bug.cgi?id=3702#desc6
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);