Intro #
Recently I was handed a thousand files that had filenames with inconsistent casing and spacing. Being on a case-insensitive system, I couldn’t just change the filenames to get Git to recognize the changes. Instead, I needed a way to have Git recognize that the filename changed.
Windows and macOS (by default) are case-insensitive meaning that they see Filename.txt
and filename.txt
as the same file. If I were to change the filename then the OS might show the new filename, but Git does not recognize this as a change. This is different than within Linux and some configurations of macOS which would treat our Filename.txt
and filename.txt
as distinct files that would be tracked by Git by changing the filename.
On case-insensitive systems, we can handle this with git mv
.
git mv #
git mv
is a useful command to move a tracked file from one location to another in one simple command as opposed to adding and removing as separate commands. This command also allows us to rename a file in the process, and we can always leave the file in-place without changing its directory.
Take, for instance, we have GOBLIN_PYRO.webp
that we need to standardize with the rest of our files as lowercase. For this example, we will assume this is in our root project file.
git mv "GOBLIN_PYRO.webp" "goblin_pyro.webp"
The first argument is the original location/name, and the second argument is the destination location/name. Once this is done, Git will successfully track the change and automatically stages it! I can now finish by making my commit:
git commit -m "Renamed GOBLIN_PYRO.webp to goblin_pyro.webp"
Looping #
I mentioned I had a thousand files like this though, so like any developer worth my collection of rubber ducks, I needed to automate this. To iterate through every file in the current directory and its subdirectories I did the following with Git Bash:
find . -type f -print0 | while IFS= read -r -d '' file; do
lower_file=$(echo "$file" | tr '[:upper:]' '[:lower:]')
if [ "$file" != "$lower_file" ]; then
git mv "$file" "$lower_file"
fi
done
This has several parts to it:
-
find . -type f -print0
: This command finds all files in the directory and its subdirectories, printing the filenames separated by a null character (\0), which safely handles filenames with spaces or special characters. -
while IFS= read -r -d '' file; do
: This loop reads each filename (-r prevents backslashes from being treated as escape characters, and -d ’’ tells read to use the null character as a delimiter). -
lower_file=$(echo "$file" | tr '[:upper:]' '[:lower:]')
: Converts the filename to lowercase. -
if [ "$file" != "$lower_file" ]; then git mv "$file" "$lower_file"; fi;
: Checks if the filename is different after conversion to lowercase, and if so, renames the file using git mv.
After running the command all of my ~1000 files were renamed and staged, ready for me to make my commit.
A word on git config core.ignorecase false
#
In git there is a core.ignorecase setting, however, the setting cannot override Windows’ filesystem limitations (or, at least it couldn’t when I tried w/ Windows 10). Even with changing the setting to false
, Git won’t detect case-only changes because Windows doesn’t treat them as real file changes. This is why we need to use git mv
for our filename casing issues. Additionally, if you change the default core.ignorecase setting then git documentation says you may run into “unexpected issues”, such as when files are moved from one OS to another if they have different ways of handling casing.
Conclusion #
Getting filenames to cooperate with Git can be tricky on case-insensitive systems. Fortunately, the git mv
command can be used to easily rename files and ensure the changes are tracked correctly. Whether you’re renaming a single file or an entire directory, git mv
will help you maintain consistency and avoid potential problems in your projects.