How do I change case of the names of multiple files, already committed?

Git ignores the casing if I push the code with lowercase images …

This is not actually true.

To understand the problem properly, you need to know the following things:

  • Git doesn’t store files, but rather commits.

  • Commits do store files, but not in the same way your computer does normally. The files stored inside commits have case-sensitive names, always.

  • git push sends commits.

In this sense, Git is perfectly capable of replacing all the uppercase-only names in commit X with new lowercase-only names in new commit Y, while keeping all the content of each file the same. That is, in commit X, you would find file X:PATH/TO/FOO.JPG, and in commit Y, file Y:path/to/foo.jpg would be “the same file” as the earlier one, except for the fact that its name is now in all lowercase instead of all uppercase.

Note that files inside commits have long paths that appear to have folder names in them. As far as Git is concerned, they’re still just files with these long paths. The fact that your computer requires that something first create a folder named path, then another one in path named to, so that it can have a file in there named foo.jpg that you’ll access as path/to/foo.jpg … well, that’s your computer’s problem; Git will do its best to adapt to it.

This is where the name-casing issue comes in as well. Your computer insists that the folder named PATH and the folder named path are the same folder. It won’t make both! If you try to create a new folder named to in the folder named path, and the folder named PATH exists, your computer insists on putting the folder named to into the folder named PATH. Worse, if the folder named TO exists in the folder named PATH, your computer insists on using that, instead of creating a new one.

If neither of these are already in the way, your computer will go ahead and create path, and then path/to. So one option you have1 is to remove everything in PATH/TO, remove the folder TO entirely, and then remove everything in PATH and remove the folder PATH entirely. Now Git can create path and path/to.

Whatever may be going on with path/to or PATH/to or path/TO or pAtH/tO or whatever, now suppose that Git wants to create or modify the file named foo.jpg within this folder. If there is no file with that name, your computer is happy to create a new one and preserve the all-lowercase name, so that you wind up with foo.jpg in there. But if there is already a file named FOO.JPG, any attempt to create a new foo.jpg and write to it just writes to the existing FOO.JPG, which retains its shouty uppercase name.

In other words, this is not Git’s problem. This is your computer’s problem. Of course, Git is running on your computer, which makes it your—and Git’s—problem to deal with. But it’s important to understand what precisely is at fault here: it’s your computer, not Git, doing this to you. If we make your Mac stop doing this to you, the problems vanish: file name case suddenly matters, the way Git thinks things should be, and everything else just works.


1Obviously, you have other options as well: you could just rename PATH to I_LIKE_YELLING_PATH, for instance, which retains all your existing files. Eventually you can rename it back, or move files out of it, or whatever.


The easiest way to fix it is to use a file system that does not fold case

On a Mac, you could spin up a VM running Linux (see, e.g., https://apple.stackexchange.com/questions/264527/linux-vm-installation-on-macbook-pro-with-macos-sierra-10-12, which is old but probably still works; I have a Linux VM with VirtualBox that I use for all kinds of things on this particular laptop). The default Linux file systems don’t think that README is the same file as ReadMe, and will let you put both files into the file system. Git works really well here, which is probably not surprising.

(As I understand it, VMs work well on Windows too, and are probably the easiest way to go there. I don’t “do” Windows, though.)

You can also make a non-case-folding file system. I do not recommend reformatting your main file system as case sensitive (see https://superuser.com/questions/203896/case-sensitive-folder-names-in-os-x), but you can make a mountable image, or use a USB stick. For instance, using the Disk Utility, create a new blank image. Name it something like case-sensitive—this will be the name for the .dmg file—and place it somewhere you can reach easily, e.g., Desktop or Downloads. Make it big enough, e.g., 1 GB should be plenty. For the Format, select a case sensitive file system such as Mac OS Extended (Case-sensitive, Journaled):

making case sensitive journaled fs

Change the Name field too, just above the Size (I had forgotten to do this at the point I made the screenshot). That is, the Save As should be a memorable name for the .dmg file, and Name should be where you want the disk to show up in /Volumes whenever you mount it. Click Save and once it’s done, click the Done button to dismiss the popup.

You can now mount this file system any time (Disk Utility has already done it for right now; later, just double click the .dmg file) and use cd /Volumes/case-sensitive in a Terminal window or tab. Here you can clone and work with case-sensitive repositories, and not have to worry about case.

Now that you have a case-sensitive local file system, clone your repository and rename your files

cd /Volumes/case-sensitive
git clone <url>
cd <clone>/<path>

You’re now ready for renaming. There are lots of ways to do this. See VonC’s answer for a sample bash script. Here is a /bin/sh shell fragment that works on MacOS:

for name in *.JPG; do
    lc=$(echo $name | sed 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')
    echo git mv $name $lc
done

Run this once to make sure you like the product, then again with the echo removed so that it actually issues git mv commands.

My own preferred method is to dump the file names into a /tmp file:

ls *.JPG > /tmp/x      # note: ls, not echo, to get one per line

then to edit the /tmp file in place into the right series of commands, in vim:

vim /tmp/x
:%s/.*/git mv & \L&/

Once the result looks right, I write out the temp file and exit (ZZ or :x) and then execute it. Remove it when done:

sh /tmp/x
rm /tmp/x

You are now ready to make any other changes needed, git commit, and git push from this case-sensitive directory within this case-sensitive file system in /Volumes/case-sensitive.

(Once you’re done with the case-sensitive-file-system .dmg file, you can eject and delete it, as you would with any .dmg file. You can also keep it as long as you like, if it’s not using up too much space.)

Leave a Comment