On Mon, May 03, 2021 at 06:25:43PM +0100, Mark Amery wrote: > Attempting to change the case of a folder's name using a command like > `git mv foo FOO` crashes on case-insensitive file systems, like the > default APFS used on Apple Macs. > > Here are simple steps to repro this: > > $ mkdir testrepo && cd testrepo && git init > Initialized empty Git repository in /Users/markamery/testrepo/.git/ > $ mkdir foo && touch foo/bar && git add foo && git commit -m bla > [master (root-commit) a7e9f5f] bla > 1 file changed, 0 insertions(+), 0 deletions(-) > create mode 100644 foo/bar > $ git mv foo FOO > fatal: renaming 'foo' failed: Invalid argument > $ echo $? > 128 > $ git status > On branch master > nothing to commit, working tree clean > > If I create a case-sensitive APFS volume using Disk Utility and try > the commands above on that volume, `git mv foo FOO` works correctly: > it emits no output, exits with a 0 status code, and stages a change > renaming `foo/bar` to `FOO/bar`. However, on my main case-insensitive > volume, `git mv` behaves as shown above: it exits with code 128, > prints an "Invalid argument" error message, and does not stage any > changes. > > The command still fails in the same way if you use `git mv --force` > instead of just `git mv`. > > Note that previously, `git mv` could not change the case of *file* > names on case-insensitive file systems, until that was fixed in commit > https://github.com/git/git/commit/baa37bff9a845471754d3f47957d58a6ccc30058. > I'm guessing there's a different code path that needs fixing for > changing the case of *folders*. > > As far as I can tell, this error has never been reported to the Git > mailing list, but it seems to be encountered frequently; > https://stackoverflow.com/questions/3011625/git-mv-and-only-change-case-of-directory > mentions this bug and has 86000 views. > > In case it's relevant, here's my system info as output by `git bugreport`: > > [System Info] > git version: > git version 2.31.1 > cpu: x86_64 > no commit associated with this build > sizeof-long: 8 > sizeof-size_t: 8 > shell-path: /bin/sh > uname: Darwin 18.7.0 Darwin Kernel Version 18.7.0: Mon Apr 27 > 20:09:39 PDT 2020; root:xnu-4903.278.35~1/RELEASE_X86_64 x86_64 > compiler info: clang: 11.0.0 (clang-1100.0.33.17) > libc info: no libc information available > $SHELL (typically, interactive shell): /bin/bash Thanks for reporting - that's always good. To my undestanding we try to rename foo/ into FOO/. But because FOO/ already "exists" as directory, Git tries to move foo/ into FOO/foo, which fails. And no, the problem is probably not restricted to MacOs, Windows and all case-insenstive file systems should show the same, but I haven't tested yet, so it's more a suspicion. The following diff allows to move foo/ into FOO/ If someone wants to make a patch out if, that would be good. diff --git a/builtin/mv.c b/builtin/mv.c index 3fccdcb6452..fbf184bcfa9 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -163,8 +163,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { - dest_path[0] = add_slash(dest_path[0]); - destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + if (!ignore_case || strcasecmp(source[0], dest_path[0])) { + dest_path[0] = add_slash(dest_path[0]); + destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + } } else { if (argc != 1) die(_("destination '%s' is not a directory"), dest_path[0]); @@ -187,9 +189,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) (dst[length] == 0 || dst[length] == '/')) { bad = _("can not move directory into itself"); } else if ((src_is_dir = S_ISDIR(st.st_mode)) - && lstat(dst, &st) == 0) - bad = _("cannot move directory over file"); - else if (src_is_dir) { + && lstat(dst, &st) == 0) { + if (!ignore_case || strcasecmp(src, dst)){ + bad = _("cannot move directory over file"); + } + } + if (!bad && src_is_dir) { int first = cache_name_pos(src, length), last; if (first >= 0) @@ -277,7 +282,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (mode != INDEX && rename(src, dst) < 0) { if (ignore_errors) continue; - die_errno(_("renaming '%s' failed"), src); + die_errno(_("renaming '%s' into '%s' failed"), src, dst); } if (submodule_gitfile[i]) { if (!update_path_in_gitmodules(src, dst))