(Skip to the TL;DR if you’re in a hurry)
It’s best not to use git on a case insensitive file system. But hindsight is 20/20, and here I am, with a messed up repo:
$ ls -1
A Space
Upper
lower
$ mv Upper/ upper/
$ ls -1
A Space
lower
upper
$ git ls-files
A Space/.gitkeep
Upper/.gitkeep
lower/.gitkeep
Oh no, git hasn’t picked up the rename, since it knows the file system is case insensitive. (In the example, it’s easy to fix, when I hit the issue, there were ~80 directories.)
First, it’s clear we need to use git commands to show the paths, not the paths on the file system, i.e. find
is out.
git ls-files
is okay, but git ls-tree
works better. Here are the switches:
-d
: “Show only the named tree entry itself, not its children.” (i.e. directories)-r
: “Recurse into sub-trees.”
--full-tree
is also useful, especially for scripts, but I’m going to ignore it.
Here’s the repo after I’ve added some nested directories for the demo:
$ git ls-tree -dr HEAD
040000 tree d564d0bc3dd917926892c55e3706cc116d5b165e A Space
040000 tree d564d0bc3dd917926892c55e3706cc116d5b165e Upper
040000 tree d564d0bc3dd917926892c55e3706cc116d5b165e lower
040000 tree 9da866107da034aefd6da5e47d18d3ea2176d9ed nested
040000 tree d564d0bc3dd917926892c55e3706cc116d5b165e nested/Upper
040000 tree d564d0bc3dd917926892c55e3706cc116d5b165e nested/lower
The output format is <mode> SP <type> SP <object> TAB <file>
, so we can split on tab (\t
, which is the default for cut
):
$ git ls-tree -dr HEAD | cut -f2
A Space
Upper
lower
nested
nested/Upper
nested/lower
For further processing, I’m going to use perl
instead of awk
. It’s more portable (there are several awk
variants), and easier to read (crazy, I know).
Here are the switches (perlrun):
-e commandline
: “may be used to enter one line of program. […]”-Fpattern
: “specifies the pattern to split on […]”-l[octnum]
: “enables automatic line-ending processing. […]”, basically strips newlines on input and adds them on output
$ git ls-tree -dr HEAD | \
> perl -lF'\t' \
> -e 'print "$F[1]";'
A Space
Upper
lower
nested
nested/Upper
nested/lower
$ git ls-tree -dr HEAD | \
> perl -lF'\t' \
> -e '$a = $F[1]; $b = lc $a; print "$a\n$b" if $a ne $b'
A Space
a space
Upper
upper
nested/Upper
nested/upper
So now I have only folders with upper case in the names, and the lower case version of that, which is useful for the rename. Using xargs
to apply two consecutive lines to a command:
-d delim
: “Input items are terminated by the specified character. […]”, need to set this to newline otherwisexargs
will split on a space also-L max-lines
: “Use at most max-lines nonblank input lines per command line. […]”
$ git ls-tree -dr HEAD | \
> perl -lF'\t' \
> -e '$a = $F[1]; $b = lc $a; print "$a\n$b" if $a ne $b' | \
> xargs -d'\n' -L2 printf '"%s" "%s"\n'
"A Space" "a space"
"Upper" "upper"
"nested/Upper" "nested/upper"
Good to see spaces aren’t an issue.
To avoid the error fatal: renaming 'foldername' failed: Invalid argument
, I’m going to use a temporary directory for the rename (via stackoverflow). It’s a good idea to check if you already have a directory called temp
, and use a different name if required.
$ git ls-tree -dr HEAD | cut -f2
A Space
Upper
lower
nested
nested/Upper
nested/lower
$ git ls-tree -dr HEAD | \
> perl -lF'\t' \
> -e '$a = $F[1]; $b = lc $a; print "$a\n$b" if $a ne $b' | \
> xargs -d'\n' -L2 \
> bash -c 'git mv "$0" "temp" && git mv "temp" "$1"'
$ git commit -m "rename"
[master 1b96fba] rename
3 files changed, 0 insertions(+), 0 deletions(-)
rename {A Space => a space}/.gitkeep (100%)
rename {Upper => nested/upper}/.gitkeep (100%)
rename {nested/Upper => upper}/.gitkeep (100%)
$ git ls-tree -dr HEAD | cut -f2
a space
lower
nested
nested/lower
nested/upper
upper
Boom, done.
TL;DR
git ls-tree -dr HEAD | \
perl -lF'\t' \
-e '$a = $F[1]; $b = lc $a; print "$a\n$b" if $a ne $b' | \
xargs -d'\n' -L2 \
bash -c 'git mv "$0" "temp" && git mv "temp" "$1"'