Git: есть файл, который показывает, что он изменился, удаляю ли я его или нет, как я могу оставить его без изменений? - PullRequest
0 голосов
/ 31 октября 2018

Я думаю, что кто-то изменил заглавную букву файла, и я не смог оставить его без изменений в пиаре. После того, как я показал git pull и git rebase -i origin/master, git status показывает, что я добавил файл, который я никогда не трогал. Я попытался запустить git rm <filename>, но затем он показывает, что я удалил файл.

Я запустил git checkout origin/master -- <filename>, что добавило обратно файл, но git status показывает, что я снова добавил файл.

Я заметил, что когда я создал коммит после удаления файла, он показывает две разные заглавные буквы. I.e.:

[branchname 45dd45ce2] wip
 2 files changed, 140 deletions(-)
 delete mode 100644 Filename
 delete mode 100644 filename

Я пытался переименовать файл в обе заглавные буквы, но Git всегда рассматривал его как новый файл.

Как я могу оставить это без изменений?

Я использую WSL с Ubuntu 18.04.

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

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 Это ужасные коммиты. Используйте что-то лучшее при работе с реальным хранилищем, а не с контрольным примером.

0 голосов
/ 31 октября 2018

Сброс локального репозитория, чтобы отразить его на удаленном, должен решить проблему для вас.

git reset --hard

Конечно, вы должны знать, что сброс вашего локального репозитория приведет к потере любой работы, еще не переданной на удаленный компьютер. Поэтому сделайте резервную копию каталога перед сбросом. (Мне нравится архивировать каталог в таких ситуациях.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...