How do I change case of the names of multiple files, already committed?
I want to rename all 500 images which I had uploaded on Git to lowercase.
Git ignores the casing if I push the code with lowercase images.
Is there any specific command to fix it?
OS: MAC
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)
:
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.)