Перевод статьи Тима Клема «Mind the End of Your Line».

Один из самых частых вопросов о Гите — почему так сложно работать с окончаниями строк. В этой статье мы попробуем ответить на этот вопрос и рассказать о множестве опций и настроек для контроля над окончаниями строк в Гите.

Гит имеет дело с двумя системами для работы с концами строк в репозиториях. Корень проблемы в том, что популярные операционные системы по-разному обозначают конец строки: Unix, Linux и Mac OS X используют LF, а Windows CRLF. В этой статье мы не будем брать во внимание, что в предыдущих версиях Mac OS X использовался CR.

Ничего из этого не было бы проблемой, если бы каждый из нас жил в своём маленьком, изолированном мире и никогда не обменивался кодом между разными операционными системами. Под обменом кодом, в данном случае, будем понимать всё — от работы над кросс-платформенным проектом до копирования кода из браузера. Всякий раз, когда вы скачиваете архив проекта с Гитхаба, копируете код из чьего-то блога или гиста или используете код из файла на облачном хранилище, вы работаете с текстом, а значит имеете дело с невидимыми символами окончаний строк.

Все эти действия с кодом потенциально могут привнести разные настройки окончаний строк в вашу кодовую базу. Это может привести к беспорядочным диффам и сделать работу с Гитом в целом неприятной.

Основное решение, которое принял Гит для этой проблемы — указать, что лучший способ хранить окончания строк в репозитории для текстовых файлов — использование LF. Это правило ни к чему вас не принуждает, но большинство разработчиков, использующих Гит и Гитхаб, приняли его как соглашение и мы тоже рекомендуем так настроить ваш конфиг.

Основы

Перед тем, как мы опишем настройки для управления окончаниями строк, нам надо узнать несколько вещей о core.eol и разобраться с тем, что значит записать что-либо в базу данных Гит.

Конец строки

core.eol — первый параметр, о котором нужно знать.

Почти всегда, кроме самых редких случаев, нам не стоит менять дефолтное значение этого параметра. Хотя сам по себе core.eol мало что делает, нам нужно знать его значение каждый раз, когда мы хотим, чтобы Гит изменил окончания строк. Так как этот параметр будет использоваться во всём, о чём пойдёт речь дальше, хорошо бы знать о его существовании и о том, что его значение, вероятно, менять не придётся.

  • core.eol = native — значение по умолчанию. При записи файла в рабочую папку, Гит изменит окончания строк на соответствующие вашей платформе по умолчанию. Для Windows это будет CRLF, для Unix, Linux и Mac OS X — LF;
  • core.eol = crlf— если установлено такое значение, Гит всегда будет использовать для обозначения конца строки CRLF при записи файла в вашу рабочую директорию;
  • core.eol = lf — это значение скажет Гиту всегда использовать LF для обозначения конца строки при записи файла в вашу рабочую папку.

Чтобы узнать, какое значение core.eol установлено в вашей системе, нужно запустить в консоли команду git config --global core.eol. Если команда ничего не вернёт, значит, в вашей системе используется значение по умолчанию, native.

Запись и вывод объектов из базы данных

Прежде чем двигаться дальше, мы поговорим о двух важных операциях: записи в объектную базу и выводе данных из неё в рабочую директорию. Возможно, вы уже знаете, что Гит хранит свою базу данных в папке .git. Он создаёт эту директорию и несколько файлов в ней, после запуска команды git init. Файлы в папке .git определяют все конфигурации Гита, в них хранится история проекта. Это обычные файлы и мы можем их читать и редактировать так же, как файлы самого проекта.

Каждый раз, когда мы делаем команду типа git commit, мы записываем объекты в эту базу данных. Запись в базу данных включает в себя:

  • сохранение всего содержимого файла;
  • добавление его в список со всеми файлами, которые отслеживает Гит;
  • создание блоб-файла;
  • вычисление SHA-ключа — хэш-кода, в котором хранится информация о содержимом файла.

Во время записи в базу данных **Гит может запустить фильтры и преобразовать окончания строк.

Есть ещё один случай, когда у Гита появляется возможность преобразовать окончания строк — это запись файлов из базы данных в нашу рабочую папку. Это то, что мы подразумеваем под выводом из базы данных. Такой процесс можно запустить множеством команд, но самая очевидная и простая для понимания — git checkout. Вывод из объектной базы данных также происходит после запуска команд, которые делают изменения в нашей рабочей папке, например, git clone или git reset.

Старая система

Теперь давайте поговорим о старой системе — оригинальном наборе функций в Гите, предназначенном для решения конкретной проблемы с окончаниями строк. Есть большая вероятность, что вы прямо сейчас пользуетесь старой системой и даже не подозреваете об этом.

Вот как это работает: у Гита есть настройка конфигураций core.autocrlf, которая специально создана для того, чтобы все окончания строк в текстовом файле преобразовывались в LF при записи в объектную базу данных репозитория. Вот список разных настроек для core.autocrlf и их значений:

  • core.autocrlf = false — это значение по умолчанию, которое большинству людей следует сменить. Результатом использования этого значения станет то, что Гит не будет связываться с окончаниями строк в ваших файлах. Там могут быть разные окончаниями строк: LF, CRLF, CR или микс из всех них, но Гиту это будет безразлично. Такое значение может привести к тому, что диффы станут менее читаемыми и появятся сложности при слиянии веток. У большинства пользователей Unix/Linux установлено именно это значение, потому что у них нет проблем с CRLF и им не нужно, чтобы Гит делал дополнительную работу каждый раз при записи файлов в базу данных или в рабочую папку.
  • core.autocrlf = true — значит, что Гит обработает все текстовые файлы и убедится, что все CRLF заменены на LF перед записью в базу данных. При обратном процессе он преобразует все LF в CRLF. Такая установка гарантирует, что ваш репозиторий можно будет использовать на других платформах, сохраняя CRLF в вашем рабочей папке. Поэтому параметр true для core.autocrlf рекомендован для Windows.
  • core.autocrlf = input — значит, что Гит обработает все текстовые файлы и убедится, что все CRLF изменены на LF при записи файлов в базу данных. Однако обратной замены не произойдёт. При записи файлов в рабочую папку из базы данных, для обозначения конца строки останутся LF. Этот параметр обычно используется в Unix / Linux / OS X для предотвращения записи CRLF в репозиторий. Идея заключается в том, что если вы вставили код из браузера и случайно записали CRLF в один из ваших файлов, Гит удостоверится, что произойдёт замена на LF при записи в базу данных.

Чтобы увидеть, какое значение для core.autocrlf установлено в вашей системе, нужно запустить в командной строке git config --global core.autocrlf . Если команда ничего не вернёт, то вы используете значение по умолчанию, false.

Как же Гит определяет, что файл текстовый? Хороший вопрос. У Гита есть внутренний эвристический метод, который проверяет, двоичный ли файл. Если файл не двоичный, то Гит считает его текстовым. Но Гит иногда может ошибаться, и это будет причиной для знакомства со следующей настройкой.

Параметр core.safecrlf был создан на тот случай, если Гит ошибётся и изменит окончания строк там, где лучше было бы оставить их в покое.

  • core.safecrlf = true — перед записью в базу данных при подготовке к замене CRLF на LF, Гит убедится, что сможет успешно прервать операцию. Он проверит, что можно откатить изменения (из LF в CRLF), а если нет, то отменит операцию.
  • core.safecrlf = warn — сделает то же, что и предыдущий параметр, но вместо того, чтобы прервать операцию, Гит просто предупредит вас о том, что может случиться что-то нехорошее.

Наконец, вы можете создать в корне своего репозитория файл .gitattributes и указать в нём настройки для конкретных файлов. Это позволит вам управлять такими настройками, как autocrlf для каждого типа файлов.

  • Например, для того, чтобы Гит заменил CRLF на LF во всех текстовых файлах, можно написать в .gitattributes такую строку:

    *.txt crlf.

  • Или можно сделать, чтобы Гит никогда не заменял CRLF на LF в текстовых файлах с помощью такой строки:

    *.txt -crlf

  • Чтобы Гит заменял CRLF на LF в текстовых файлах только при записи в базу данных, но возвращал LF при записи в рабочий каталог, нужно написать:

    *.txt crlf=input

Хорошо, видите, какой беспорядок мы тут учинили? И он становится ещё больше, если мы начинаем работать над проектами, которые подталкивают нас к другим глобальным настройкам. Введём в дело новую систему, доступную начиная с версии Гит 1.7.2.

Новая система

Новая система определяет все настройки для окончаний строк в файле .gitattributes вашего репозитория, инкапсулируя их внутри и делая независимыми от глобальных настроек.

В новой системе за то, чтобы указать Гиту, в каких файлах надо заменить CRLF на LF, отвечаете вы, сообщая об этом с помощью атрибута text в файле .gitattributes. В этом случае будет полезен мануал для .gitattributes, а ниже вы найдёте несколько примеров использования атрибута text.

  • *.txt text — устанавливает атрибут text для всех текстовых файлов. Это значит, что Гит будет запускать процесс замены CRLF на LF каждый раз при записи в БД и делать обратную замену при выводе из базы данных в рабочий репозиторий.
  • *.txt -text — снимет со всех текстовых файлов этот фильтр. Это значит, что в указанных файлах не будет замены CRLF на LF.
  • *.txt text=auto — установит для всех, подходящих под условие файлов, замену CRLF на LF, если Гит с помощью своего эвристического метода определит эти файлы как текстовые, а не бинарные.

Если файл не определён, Гит вернётся к старой системе и настройке core.autocrlf.

Именно так работает обратная совместимость, но я рекомендую, особенно тем, кто использует Windows для разработки, явно создавать файл gitattributes.

Ниже пример файла .gitattributes с общими настройками, который можно использовать для своего проекта. Пример взят отсюда.

# Общие настройки, которые всегда должны быть использованы для настроек вашего языка.
# Автоматическое определение текстовых файлов и выполнение нормализации LF 
# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
* text=auto

#
# Строка выше будет обрабатывать все файлы, которых нет в списке ниже.
#

# Documents
*.doc     diff=astextplain
*.DOC     diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot  diff=astextplain
*.DOT  diff=astextplain
*.pdf  diff=astextplain
*.PDF     diff=astextplain
*.rtf     diff=astextplain
*.RTF     diff=astextplain
*.md text
*.tex text
*.adoc text
*.textile text
*.mustache text
*.csv text
*.tab text
*.tsv text
*.sql text

# Graphics
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tif binary
*.tiff binary
*.ico binary
# SVG по дефолту рассматривается как бинарный. Если вы хотите, чтобы он распознавался как текст, закомментируйте  следующую строку и раскомментируйте следующую за ней.
*.svg binary
#*.svg text
*.eps binary

#
# Исключить файлы из экспорта
#

.gitattributes export-ignore
.gitignore export-ignore

Как вы могли заметить, с помощью следующей команды можно сказать Гиту обнаруживать все текстовые файлы и автоматически конвертировать в них CRLF в LF:

* text=auto

Это, конечно, лучше, чем требовать от всех одной глобальной настройки для core.autocrlf, но и означает, что вы полностью доверяете Гиту в определении бинарности файлов. На наш взгляд, лучше явно указать текстовые файлы, которым требуется нормализация. Если вы собираетесь использовать этот параметр, не забудьте, что он должен быть указан на первой строке файла .gitattributes, чтобы последующие строки могли его переопределить.