Почему мой файл .gitattributes не препятствует добавлению \rs при извлечении файлов в Windows?

Я использую Git версии 2.28.0.windows.1 в оболочке Cygwin в Windows 10. После того, как я клонирую свой репозиторий, я вижу это

$ cat .gitattributes 
* text=auto
*.sh text eol=lf

Я настроил это, думая, что это исправит плохие окончания строк (я хочу исключить автоматическое включение окончаний строк \r). Однако после того, как я сделаю свой клон

git clone https://github.com/chicommons/maps.git
cd maps

Я все еще вижу окончания строк, которые мне не нужны...

$ grep '\r' web/entrypoint.sh
python manage.py migrate
python manage.py migrate directory
python manage.py docker_init_db_data

Что я могу сделать с моим .gitattributes (или, возможно, с другим файлом?), Чтобы предотвратить появление этих окончаний строк?


person Dave    schedule 27.09.2020    source источник
comment
Что-то не так с использованием стандартной конфигурации git? git config --global core.autocrlf input выдаст вам LF при оформлении заказа.   -  person zrrbite    schedule 27.09.2020


Ответы (2)


Директива eol=lf предотвратит добавление Git символов возврата каретки, но не помешает Git сохранить существующие символы возврата каретки.

Чтобы по-настоящему понять, что здесь происходит, требуется немного знаний о том, как Git хранит файлы внутри коммитов. Ключи к этому:

  • Каждый коммит хранит полный снимок каждого файла в формате только для чтения, сжатом, только для Git и без дублирования. Это означает, что файлы, которые вы на самом деле видите и с которыми работаете, находятся не в репозитории: файлы, которые вы используете, находятся в вашем рабочем дереве или рабочем дереве< /эм>.

  • Все части любого коммита, включая все его файлы, буквально неизменяемы. Если вы берете внутренний объект Git (включая фиксацию) из репозитория, каким-то образом изменяете его и кладете обратно, вы не меняете оригинал, а просто добавляете еще один, и этот новый получает другой хэш-идентификатор.

  • Чтобы получить файлы из коммита в ваше рабочее дерево, Git должен их скопировать. Это очевидно; что не очевидно, так это то, что существует третья копия каждого файла. Эта третья — или, на самом деле, средняя — копия находится в том, что Git называет индексом, или индексом, или — редко в наши дни — в кэше. Все три имени относятся к одному и тому же объекту.

То есть предположим, что HEAD присоединено к имени ветки master, а master в настоящее время представляет фиксацию, хэш-идентификатор которой равен a123456.... Другими словами, этот коммит — с его большим уродливым хэш-идентификатором — является вашим текущим коммитом. Внутри этой фиксации у нас есть файлы с именами README.md и main.py и — в вашем случае — web/migrate.sh. Существует три копии этого файла. Копии здесь заключены в кавычки, потому что две из них находятся в формате с автоматическим удалением дубликатов, поэтому на самом деле существует только одна базовая копия.

Мы можем проиллюстрировать эти три копии в таблице, используя специальное имя HEAD для ссылки на коммит a123456... (текущий коммит):

    HEAD              index           work-tree
--------------    --------------    --------------
README.md         README.md         README.md
main.py           main.py           main.py
web/migrate.sh    web/migrate.sh    web/migrate.sh

Откуда взялись эти файлы? Что ж, когда вы впервые клонируете репозиторий, ваш Git получает все коммиты от какого-то другого Git. Эти коммиты абсолютно одинаковы в каждом Git и имеют одинаковые хэш-идентификаторы во всех Git. Затем ваш Git копирует один из этих коммитов — тот, который вы проверяете, — в индекс вашего Git и копирует файлы из своего индекса в ваше рабочее дерево. Вот где у вас есть три копии каждого файла.

Файлы рабочего дерева — это обычные повседневные файлы, которые вы можете читать и записывать с помощью любой программы на вашем компьютере. Другие файлы нет. Когда (или после) вы выполнили некоторую работу с копией рабочего дерева одного из ваших файлов, вы запускаете на ней git add. Причина этого в том, что git add сообщает Git: сделайте копию индекса соответствующей копии рабочего дерева. Например, если вы изменили main.py, версия main.py в индексе теперь отличается от версии main.py в репозитории:

    HEAD              index           work-tree
--------------    --------------    --------------
README.md(1)      README.md(1)      README.md
main.py(1)        main.py(2)        main.py
web/migrate.sh(1) web/migrate.sh(1) web/migrate.sh

Копия, которая находится в коммите, буквально неизменяема, поэтому HEAD — что на данный момент является сокращением от commit a123456... — всегда будет содержать эти три версии файлов. Но индекс, хотя и использует внутренний формат, не является фиксацией1 и не доступен только для чтения. Таким образом, git add может заменить копию индекса.

(Выполнение git commit берет все, что есть в индексе, и использует его для создания новой фиксации. Новая фиксация затем становится текущей фиксацией, так что имя HEAD и имя текущей ветки теперь относятся к новый коммит вместо коммита a123456.... Но нам пока не нужно заходить так далеко.)


1Что это такое, это, немного сложно, но в первом приближении вы можете думать об индексе как о хранении вашего предлагаемого следующего коммита. Каждый раз, когда вы проверяете какую-либо фиксацию, Git должен настроить индекс так, чтобы он был готов к следующей фиксации: обычно, заполняя его из только что извлеченной фиксации.


Копирование из индекса или в него происходит, когда Git корректирует окончания строк.

Копия файла в индексе Git находится в сжатом, предназначенном только для Git, дедуплицированном формате. Копия файла в вашем рабочем дереве имеет обычный повседневный компьютерный формат. Таким образом, каждый раз, когда Git копирует из Git-индекса в ваше рабочее дерево, он должен расширить файл; и каждый раз, когда Git копирует из вашего рабочего дерева в его индекс, он должен сжимать и удалять дубликаты файла.

Этот процесс копирования — идеальное время для внесения любых изменений в файл, которые вы хотите. Вот тут-то и вступают в игру .gitattributes и окончание строки. Предположим, что файл в индексе, который попал туда, находясь в репозитории, имеет строки, заканчивающиеся новой строкой, только с \n. Предположим, однако, что вы хотите, чтобы ваша копия файла work-tree имела окончание строки \r\n или CRLF.

Если Git превращает \n в \r\n на пути из индекса и превращает \r\n в \n на пути входа в индекс, это достигает вашей цели. Это то, что * text eol=crlf сделает.

Но что, если вы этого не хотите? Что, если вы хотите, чтобы \n концовки оставались \n концовками? Это то, что * text eol=lf сделает. Как \n окончания остаются \n окончаниями? не внося никаких изменений.

Таким образом, * text eol=lf означает не вносить изменения. Но что, если файл внутри репозитория, который поэтому копируется в индекс, имеет окончания строки \r\n (CRLF)? Что ж, тогда то же самое можно сказать и о вашем файле рабочего дерева.

Чтобы некоторые файлы в репозитории имели окончание строки, состоящее только из \n, вам необходимо:

  1. удалить \r из копий рабочего дерева;
  2. git add результирующие файлы; а также
  3. git commit, чтобы сделать новый коммит.

Затем этот новый коммит можно распространить на все другие копии этого репозитория и использовать вместо существующего (плохого) коммита, который имеет окончания \r\n (CRLF) для этих файлов.

Обратите внимание, что плохая фиксация будет продолжать существовать: в этом и заключается контроль версий. Мы не устраняем плохие, потому что они есть и у всех остальных, и мы будем помнить, что они используют плохой.

Теперь, если ни у кого больше нет копии этого репозитория или есть неверная фиксация, мы находимся в особой ситуации. В этом случае мы можем отказаться от плохой фиксации в пользу новой улучшенной фиксации. (Как именно это сделать в Git — это тема для другого ответа.) Но в целом мы просто добавляем исправление, а оригинал сохраняем.

person torek    schedule 27.09.2020

grep не использует escape-последовательности языка C в своих шаблонах поиска, он имеет очень ограниченный набор. Вы находите буквальные rs в этих строках.

Попробуйте grep $'\r' web/entrypoint.sh, чтобы узнать, что случилось, или grep --color=always '\r' web/entrypoint.sh.

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

person jthill    schedule 27.09.2020