TL; DR: ваша система менее способна, чем настоящий Linux, и это вас укусило
Система Linux, которая сделала коммит, хранила два файла, имя которых отличается только регистром. Ваша система не может этого сделать, поэтому вы не можете работать в этой среде на своей собственной системе, по крайней мере, не напрямую. Если вы раскручиваете виртуальную машину Linux внутри своей системы Windows, вы можете работать с ней там. Но - это общий метод решения этой проблемы в вашей собственной системе, который я покажу в последнем разделе длинной части. У него есть некоторые недостатки, но он может позволить вам добиться прогресса.
(Действительно, лучшее решение - развернуть экземпляр Linux и исправить его напрямую.)
Long
Я заметил, что когда я создал коммит после удаления файла, он показывает две разные заглавные буквы. I.e.:
[branchname 45dd45ce2] wip
2 files changed, 140 deletions(-)
delete mode 100644 Filename
delete mode 100644 filename
Это означает, что в коммите, который вы извлекли (родитель нового коммита 45dd45ce2
), внутри него есть оба варианта написания этого имени файла. Linux может сделать это, но Windows не может. 1
1 Технически, это зависит от файловой системы. Проблема возникает в файловых системах, которые закрывают регистр, и Windows и MacOS делают это по умолчанию, в то время как Linux не делает этого по умолчанию. Очевидно, WSL использует базовую файловую систему Windows по умолчанию, тем самым импортируя ее функции и ограничения.
Давайте сначала сделаем шаг назад и посмотрим, что Git действительно делает с коммитами. Помните, что коммит содержит полный снимок некоторого набора файлов. Для каждого файла хранилище фиксации - это имя файла, его флаг разрешения на выполнение и его содержимое. Сам коммит идентифицируется уникальным, большим уродливым хеш-идентификатором, например, 4ede3d42dfb57f9a41ac96a1f216c62eb7566cc2
(это коммит в репозитории Git для самого Git). Этот коммит также хранит среди прочих данных сообщение журнала, имя автора коммита и адрес электронной почты, а также родительский хэш-идентификатор; но в данный момент мы собираемся сосредоточиться на файлах, хранящихся в коммите, с особым вниманием к их именам. Однако сначала давайте кратко рассмотрим содержание, поскольку эта часть также представляет интерес.
Каждый файл хранится внутри каждого коммита. Если бы Git хранил новую копию каждый раз, это быстро сделало бы ваш репозиторий очень большим. Так что Git не хранит новую копию, если это просто. В частности, если новый коммит использует одинаковое содержимое для большинства своих файлов, Git просто повторно использует старое содержимое в новом коммите . Это означает, что Git лучше не трогать содержимое существующих сохраненных файлов, так что это не так: они доступны только для чтения. Между тем, чтобы уменьшить размер хранилища, Git также сжимает это содержимое. Таким образом, содержимое каждого файла, хранящееся в репозитории Git, находится в специальной, доступной только для чтения (следовательно, совместно используемой), сжатой, иногда очень сжатой, форме только для Git. (Git называет эти blob . "Blob" - это один из четырех внутренних типов объектов Git, остальные три - tree , аннотированный тег и commit . Между тем, имена хранятся в этих «древовидных» объектах. Вам не нужно знать эти детали, но иногда они полезны.)
Как только он сделан, каждый коммит также доступен только для чтения. Фактически, это верно для всех внутренних объектов Git. Хэш-идентификатор каждого объекта - это просто криптографическая контрольная сумма данных объекта. Это позволяет Git быть уверенным в том, что данные не повреждены, когда он снова смотрит на объект позже: текущая контрольная сумма данных должна соответствовать хэш-идентификатору, используемому для поиска объекта. Если они совпадают, данные верны; если нет, то что-то испортило коммит. Это , почему вы не можете изменить коммит: если вы измените какие-либо данные, контрольная сумма изменится, и вместо этого вы получите новый и другой коммит. Но дело в том, что мы обеспокоены тем, что когда-то сделанный коммит застыл во времени: внутри него ничего не может измениться, и это включает в себя имена файлов.
Тем не менее, весь репозиторий Git в его специальной форме Gitty может быть перенесен в другую систему. Как только это произойдет, эти коммиты могут быть извлечены ... ну, вроде. Здесь начинаются проблемы.
Когда Git извлекает коммит из хранилища, он должен скопировать замороженные, доступные только для чтения большие двоичные объекты из морозильника, разморозить их и поместить в обычный повседневный формат, чтобы вы могли фактически использовать файлы. Git делает это в два этапа: сначала он копирует замороженный объект в index Git, где он не заморожен, но все еще находится в специальном сжатом формате Git-only, используя собственный внутренний метод Git для запоминания имени файла и выполнения бит разрешения, а затем он распаковывает замороженный большой двоичный объект в ваше рабочее дерево , где вы можете работать с ним.
Это последний шаг, когда дела идут плохо. Git должен создать один файл с именем Filename
, а другой - другой файл с именем filename
. В Linux это просто: просто позвоните создателю файла с двумя именами. В Windows , если вы это сделаете, файл second перезаписывает первый, сохраняя любое имя, которое вы использовали первым.
Это означает, что независимо от того, что вы делаете, в вашем рабочем дереве останется только один файл, даже если у вас есть оба файла в вашем коммите (в специальном замороженном формате Git-only) и в вашем индексе (в специальном формате Git-only, незамерзшем). Эта ситуация сложная и болезненная. Однако Git new фиксирует из индекса, поэтому еще не все потеряно.
Обходной путь
В вашей системе Windows или MacOS - файловые системы Mac имеют такую же проблему, как мы видели в сноске 1, - сделать новый коммит, в котором одно из двух имен в индексе было переименованы. Я начал с создания репозитория с тремя файлами:
$ mkdir case
$ cd case
$ git init
Initialized empty Git repository in ...
$ echo test case issues > README
$ echo THIS FILE USES UPPERCASE > FILENAME
$ echo this file uses lowercase > filename
$ ls
filename FILENAME README
$ git add *
$ git commit -m initial
[master (root-commit) 46e94a6] initial
3 files changed, 3 insertions(+)
create mode 100644 FILENAME
create mode 100644 README
create mode 100644 filename
Затем я клонировал этот репозиторий на Mac:
$ git clone ssh:[url]
Cloning into 'case'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 5 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (5/5), done.
$ cd case
$ git status --short
M FILENAME
$ ls
README filename
$ git ls-files
FILENAME
README
filename
Хитрость заключается в том, чтобы переименовать один из двух файлов в индексе. Мне вообще не нравятся все заглавные буквы, поэтому давайте теперь переименуем заглавные:
$ git mv FILENAME UC-FILENAME
(возможно, мне следовало бы mv
-дать его к имени yucky-filename
:-)). Можно использовать git ls-files
, чтобы проверить, что это сработало (или git ls-files --stage
, чтобы получить подробную версию), и я это сделал, но я просто покажу коммит следующий: 2
$ git commit -m 'fix case-collision'
[master 7712644] fix case-collision
1 file changed, 0 insertions(+), 0 deletions(-)
rename FILENAME => UC-FILENAME (100%)
Теперь нам нужно исправить рабочее дерево, которое не синхронизировано с индексом и репозиторием. Самый простой способ сделать это - использовать git reset --hard
:
$ git reset --hard
HEAD is now at 7712644 fix case-collision
$ ls
README UC-FILENAME filename
$ cat UC-FILENAME
THIS FILE USES UPPERCASE
$ cat filename
this file uses lowercase
Теперь мы можем отодвинуть это назад, если получающий репозиторий был --bare
(это не так), но дело в том, что теперь мы можем работать с файлами изначально (в данном случае, на этом конкретном Mac), так как они больше не конфликтует с собственной файловой системой.
2 Это ужасные коммиты. Используйте что-то лучшее при работе с реальным хранилищем, а не с контрольным примером.