The typical use case is when a file "FileA" should be renamed into fILEa and we are on a case insenstive file system (system core.ignorecase = true). Source and destination are the same file, it can be accessed under both names. This makes git think that the destination file exists. Unless used with --forced, git will refuse the "git mv FileA fILEa". This change will allow "git mv FileA fILEa", when core.ignorecase = true and source and destination filenames only differ in case and the file length is identical. On Linux/Unix/Mac OS X the mv is allowed when the inode of the source and destination are equal. On this allows renames of MÃRCHEN into MÃrchen on Mac OS X. (As a side effect, a file can be renamed to a name which is already hard-linked to the same inode) Signed-off-by: Torsten BÃgershausen <tboegi@xxxxxx> --- builtin/mv.c | 20 +++++++++++++++----- t/t7001-mv.sh | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/builtin/mv.c b/builtin/mv.c index 93e8995..e0aad62 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -62,7 +62,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) }; const char **source, **destination, **dest_path; enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; - struct stat st; + struct stat st, st_dst; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; git_config(git_default_config, NULL); @@ -164,15 +164,25 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } } else if (cache_name_pos(src, length) < 0) bad = "not under version control"; - else if (lstat(dst, &st) == 0) { + else if (lstat(dst, &st_dst) == 0) { + int allow_force = force; bad = "destination exists"; - if (force) { + /* Allow when src and dst have the same inode (Mac OS X) */ + /* Allow when ignore_case and same file length (Windows) */ + if (((st_dst.st_ino) && (st_dst.st_ino == st.st_ino)) || + ((ignore_case) && !strcasecmp(src, dst) && + (st.st_size == st_dst.st_size))) { + allow_force = 1; + bad = NULL; + } + if (allow_force) { /* * only files can overwrite each other: * check both source and destination */ - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - warning("%s; will overwrite!", bad); + if (S_ISREG(st_dst.st_mode) || S_ISLNK(st_dst.st_mode)) { + if (bad) + warning("%s; will overwrite!", bad); bad = NULL; } else bad = "Cannot overwrite"; diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index a845b15..d0e73ee 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -255,4 +255,33 @@ test_expect_success SYMLINKS 'git mv should overwrite file with a symlink' ' rm -f moved symlink +touch x +if ln x y 2>/dev/null; then + hardlinks=1 +fi +rm -f x y + +if test "$(git config --bool core.ignorecase)" = true -o "$hardlinks"; then + test_expect_success 'git mv FileA fILEa' ' + + rm -fr .git * && + git init && + echo FileA > FileA && + git add FileA && + git commit -m add FileA && + { + if ! test -f fILEa; then + ln FileA fILEa + fi + } && + git mv FileA fILEa && + git commit -m "mv FileA fILEa" && + rm -f FileA fILEa && + git reset --hard && + test "$(echo *)" = fILEa + ' +else + say "Neither ignorecase nor hardlinks, skipping git mv FileA fILEa" +fi + test_done -- 1.7.4 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html