Разработчик поменял одну строчку в конфиге, открывает Pull Request для ревью, а GitHub показывает файл целиком красным и зеленым, будто переписаны все пятьсот строк. Ревьюер в панике, автор клянется, что менял одну строку. Оба правы, просто где-то между Windows и Linux затесались невидимые символы конца строки.
Откуда берутся два вида line endings
Unix-системы, включая Linux и macOS, завершают строку одним символом перевода строки, который принято называть LF. Windows исторически использует пару из двух символов, возврат каретки и перевод строки, эту пару называют CRLF. Для человека, читающего файл в обычном редакторе, разницы не видно вообще, но для git это два физически разных набора байт.
Unix LF: line1\n
Windows CRLF: line1\r\n
Если один разработчик на Windows сохраняет файл со своими CRLF окончаниями, а до этого файл в репозитории хранился с LF, git видит изменение буквально каждой строки файла целиком, потому что технически изменился каждый байт перехода строки, даже если видимый человеку текст не поменялся ни на один символ.
Файл .gitattributes как решение
Git позволяет явно задать правила нормализации line endings через файл .gitattributes в корне репозитория. Это правило применяется одинаково ко всем разработчикам, независимо от их локальных настроек git или операционной системы.
* text=auto eol=lf
Эта строка говорит git автоматически определять текстовые файлы и при коммите в репозиторий всегда хранить их с LF окончаниями, вне зависимости от того, с какими окончаниями строк файл лежит в рабочей директории конкретного разработчика в моменте.
Более точная настройка по типам файлов
| Паттерн | Правило | Зачем нужно |
|---|---|---|
| Общий паттерн для всех файлов | text=auto eol=lf | Базовое правило нормализации по умолчанию |
| Shell скрипты с расширением sh | text eol=lf | CRLF ломает выполнение shell скрипта в Linux |
| Батники Windows с расширением bat | text eol=crlf | Такие скрипты традиционно ожидают именно CRLF |
| Изображения png и jpg | binary | Бинарные файлы вообще не должны обрабатываться как текст |
* text=auto eol=lf
*.sh text eol=lf
*.bat text eol=crlf
*.png binary
*.jpg binary
Порядок правил в файле важен: общее правило для всех файлов должно идти первым, а более специфичные паттерны для конкретных расширений следует писать после него, чтобы они переопределяли общее поведение именно там, где это нужно.
Что делать с уже существующим репозиторием
Просто добавить .gitattributes в проект недостаточно для уже существующих файлов, git не пересканирует историю задним числом сам по себе. Нужна одна дополнительная команда, которая приведет уже отслеживаемые файлы в соответствие новым правилам.
git add --renormalize .
git status
Первая команда пересматривает все файлы репозитория с учетом новых правил .gitattributes и помечает те, что нужно перевести на правильные line endings. Вторая просто показывает, что реально изменится, прежде чем делать коммит с этой нормализацией.
Диагностика: как понять, что проблема именно в line endings
Если diff файла выглядит подозрительно большим для небольшого реального изменения, быстрая проверка через терминал покажет, какие именно окончания строк использует файл.
file example.txt
Команда file в большинстве Linux дистрибутивов явно укажет в выводе, если в текстовом файле обнаружены CRLF окончания строк, это самый быстрый способ подтвердить подозрение без открытия файла в специальном hex редакторе.
Настройка на уровне самого git, а не только .gitattributes
Помимо .gitattributes, у самого git есть глобальная настройка core.autocrlf, которая тоже влияет на конвертацию окончаний строк, но работает на уровне локальной машины разработчика, а не всей команды одинаково.
git config --global core.autocrlf input
Значение input подходит для Linux и macOS, оно означает конвертировать CRLF в LF при коммите, но не трогать окончания строк при выводе файла в рабочую директорию. Для Windows разработчиков обычно рекомендуют значение true, которое конвертирует в обе стороны. Однако полагаться только на локальную настройку core.autocrlf рискованно, потому что она зависит от того, настроил ли ее конкретный разработчик у себя, тогда как .gitattributes в репозитории работает для всех одинаково независимо от локальных настроек.
Итоговый чеклист
Заведите файл .gitattributes в самом начале проекта, до того как в репозиторий попадет большое количество файлов с смешанными line endings.
Используйте общее правило нормализации для всех файлов, и добавляйте более специфичные исключения только там, где это действительно нужно, например для батников Windows.
Для уже существующего репозитория с проблемой обязательно выполните команду с флагом renormalize, простое добавление файла правил задним числом ничего не исправит само по себе.
Не полагайтесь только на локальную настройку core.autocrlf у каждого разработчика, файл .gitattributes в самом репозитории работает надежнее, потому что не зависит от того, настроил ли что-то конкретный человек у себя на машине.
Если нужно быстро собрать готовый файл .gitattributes под конкретный стек технологий проекта, для этого есть отдельный .gitattributes Generator.