Что на самом деле делает настройка Git auto-crlf?

Я прочитал много других вопросов, касающихся настройки auto-crlf в Git, и я не спрашиваю, какую настройку auto-crlf мне следует использовать или как нормализовать окончания строк в проекте. Мой вопрос связан с пониманием самой настройки auto-crlf.

Краткая информация:

Я начал проект в Linux, но теперь начинаю работать над ним и в системе Windows. В репозитории и в Linux мои файлы используют окончания строк LF. Однако, несмотря на то, что в моей системе Windows для auto-crlf установлено значение «true» (поскольку я не клонировал проект), Git считает определенные файлы «модифицированными», если единственное различие заключается в окончаниях строк.

Он считает файлы «модифицированными» только в том случае, если я открываю файл, вношу изменения, сохраняю, а затем отменяю все изменения (CRTL + Z или ручная отмена) и снова сохраняю. Каждая утилита diff, которую я использую, говорит мне, что окончания строк являются единственной разницей (LF в репозитории и CRLF на локальном компьютере).

До недавнего времени я всегда думал, что этот параметр влияет на то, какие файлы считаются измененными в дополнение к преобразованию. Но после второго прочтения описания в сочетании с поведением, которое я испытываю, я начинаю думать, что оно преобразует только окончания строк при фиксации/проверке и не имеет ничего общего с определением того, какие файлы были изменены.

Здесь я читаю описание этой настройки.

Предполагается ли, что этот параметр также влияет на то, какие файлы считаются измененными, помимо обработки преобразования?

ИЗМЕНИТЬ:

Просто хотел добавить к моей конкретной «фоновой» ситуации для всех, кто разделяет подобное поведение. Прочитав ответ toreks, я смог определить, что моя IDE автоматически «добавляла» файлы в Git при сохранении. Это вызывало изменение «mtime», что было корнем «кажущегося» странным поведения.


person d.lanza38    schedule 19.09.2017    source источник


Ответы (1)


Истинный ответ сложен и связан с двойственной природой индекса Git, который является одновременно «площадкой подготовки» и «кэшем».

Здесь также стоит подумать о фильтрах размытия и фильтрах очистки в Git. По сути, все преобразования LF/CRLF представляют собой форму размазывания и очистки.

Всегда есть три вещи, вызывающие озабоченность

Всякий раз, когда вы работаете с репозиторием Git, вы должны помнить о трех вещах:

  • текущая фиксация, также известная как HEAD. (В файле .git/HEAD хранится часть или вся эта информация: обычно он содержит имя ветки, а затем само имя ветки содержит остальную информацию, а именно текущий идентификатор хэша коммита. В режиме "detached HEAD" сам .git/HEAD содержит хэш-идентификатор.)

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

  • индекс. Хотя лучшее обычное описание индекса — это «что войдет в следующую фиксацию», фактическая форма индекса довольно сложна, поэтому мы пока отложим подробности. Именно здесь индекс начинает играть свою роль «кэша».

  • рабочее дерево. Как следует из названия, именно здесь вы выполняете свою настоящую работу. В нем все файлы в обычном формате, так что все ваши программы и инструменты работают с ними.

    «Нормальный формат» является ключевой фразой здесь: нормальный формат в Unix-системе заключается в том, что строки заканчиваются новой строкой, в то время как нормальный формат для некоторых элементов Windows заключается в том, что строки заканчиваются CRLF или '\r\n'. (Здесь мы просто притворимся, что все файлы Windows такие, хотя на самом деле только большинство файлов, причем двоичные файлы являются первым очевидным камнем преткновения.)

    Если вы думаете о смазывании и очистке фильтров, файл в рабочем дереве находится в «размазанной» форме. То есть, если у вас работает что-то вроде Git-LFS, Git-LFS разрешено изменять версию файла рабочего дерева, чтобы она каким-то существенным образом отличалась от зафиксированной версии. (В частности, Git-LFS обманом заставляет Git сохранить только указатель на фактический файл, а затем Git-LFS извлекает реальный — и, предположительно, слишком большой для GitHub или чего-то еще — файл откуда-то еще, так что в вашей работе -tree здесь вообще не регистрируется!)

Обратите внимание, что индекс находится «между» фиксацией HEAD только для чтения и рабочим деревом. Это означает, что файлы можно копировать из HEAD в индекс, или из индекса в рабочее дерево, или из рабочего дерева в индекс. (Они не могут быть скопированы из индекса в HEAD, кроме как путем создания новой фиксации, которая затем становится текущей фиксацией, поскольку все фиксации доступны только для чтения.)

Фиксированные файлы только для чтения могут иметь только один формат

Это довольно очевидно, но стоит упомянуть. Если файлы внутри репозитория имеют формат завершения новой строки, они не соответствуют обычному формату для Windows. Что-то должно переводиться туда и обратно.

Перевод выполняется, как отмечается в книге Pro Git, во время копирования файлов в индекс и из него. Но есть три таких возможных места: если мы копируем из HEAD в индекс, это помещает (копию) файла в индекс; если мы копируем из индекса в рабочее дерево, это делает копию в этом направлении; и если мы скопируем из рабочего дерева в индекс, это сделает копию в другом направлении. Теперь фактический формат индекса и какие копии нас интересуют, начинают иметь значение.

Формат индекса

Формат индекса сложен. Чтобы прямо сейчас увидеть фактический индекс в удобочитаемой форме, запустите git ls-files --stage --debug, которая выводит много информации. (Хотя даже с --debug здесь упускаются некоторые детали.) Наиболее важные и интересные части — это то, что вы видите даже без --debug, например:

100644 4646ce575251b07053f20285be99422d6576603e 0       xdiff/xutils.h

Первое значение — это «режим» файла (всегда 100644 или 100755 для обычного файла), второе — идентификатор хэша Git, третье — номер стадии (обычно ноль), а последнее — имя файла. .

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

Это, в свою очередь, означает, что файл хранится в индексе в его «очищенной» форме (с преобразованием CRLF только в LF или Git-LFS, заменяющим весь файл указателем). На самом деле очищенные данные уже заранее записаны в репозиторий Git, а в индексе хранится только его хэш блоба! Это один из приемов, позволяющих ускорить работу Git: запись индекса содержит только хэш-идентификатор (и путь, и режим, и номер стадии, и все эти --debug выходные данные).

Это также означает, что во время копирования из индекса в рабочего дерева происходит любое смазывание (для превращения LF в CRLF или извлечения реальных файлов из Git-LFS). Любая очистка, чтобы превратить CRLF в LF-only или сохранить новый файл вне Git и обновить указатель, происходит во время копирования из рабочего дерева в индекс.

Наконец, что еще это означает, так это то, что Git не может легко сказать, только из файла рабочего дерева, актуальна ли индексная версия файла или нет. Изменена ли версия рабочего дерева? Единственный способ быть уверенным — выполнить новую полную очистку и посмотреть, получите ли вы тот же хэш-идентификатор для результирующих данных; или выполните новое полное извлечение и посмотрите, получите ли вы тот же файл рабочего дерева. Но этот процесс медленный: на самом деле он может занять десятки миллисекунд, даже если вам не нужно проходить через Git-LFS и извлекать или сохранять копию реального файл в другом месте. Умножить на множество файлов, и это слишком медленно. (В действительно большом репозитории git checkout коммита может занять буквально секунд, и это будет означать, что git status и другие подобные команды будут такими же медленными.)

Кэширование на помощь... вроде

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

  ctime: 1500043102:605208000
  mtime: 1500043102:605208000

Эти две метки времени — «время изменения индекса» и «время изменения индекса», которые Git копирует из результата системного вызова stat или lstat в файле рабочего дерева. Пока базовая система обновляет временные метки рабочего дерева всякий раз, когда изменяется файл рабочего дерева, Git может просто сравнивать текущие временные метки в файле рабочего дерева с сохраненными временными метками в индексе. (Git таким же образом сохраняет размер файла рабочего дерева.) Если метки времени совпадают, файл должен быть «чистым». Если метки времени в файле рабочего дерева новее, чем в кеше, файл может быть грязным, и мы должны проделать дополнительную работу, чтобы выяснить это наверняка. (На практике метки времени в самом индексном файле, здесь также играют роль, поскольку одна секунда — это очень много времени с точки зрения вычислений. Подробнее см. по этой ссылке.)

Ничто из этого волшебства не учитывает текущие настройки CRLF.

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

Он считает файлы «модифицированными» только в том случае, если я открываю файл, вношу изменения, сохраняю, а затем отменяю все изменения (CRTL + Z или ручная отмена) и снова сохраняю.

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

Повторение, когда происходят конверсии

... Я начинаю думать, что [Git] преобразует только окончания строк при фиксации/проверке...

Это в основном правильно. Преобразование из CRLF в LF-only произойдет:

  • на git add, который копирует из рабочего дерева в индекс, или что-либо, что вызывает git add или его базовый код (включая добавление для git commit -a или git commit [--only | --include] -- <paths>)
  • при условии, что файл помечен для такого рода «очистки»: он классифицируется как text и вы включили для него преобразование CRLF в LF.

При этом происходит преобразование из LF-only в CRLF:

  • на git checkout, когда он копирует из индекса в рабочее дерево, или несколько других более неясных связанных случаев (например, git read-tree -u)
  • при условии, что файл помечен для такого рода «смазывания»: он классифицируется как text и вы включили для него преобразование LF в CRLF.

Обратите внимание, что то, классифицируется ли файл как text, зависит от многих параметров. Как правило, все, что находится в .gitattributes, переопределяет настройки core.*, но если ничего не задано в .gitattributes, будут применяться настройки core.*.

Некоторые другие инструменты, такие как git show и git cat-file -p, теперь могут выполнять преобразование текста с помощью опций (раньше git show <commit>:<path> показывал только очищенные данные, а не нечеткую форму). И уже довольно давно git merge поддерживает концепцию «перенормализации»: выполнение виртуального извлечения и возврата перед сравнением и объединением различий базового коммита и двух коммитов для слияния.

person torek    schedule 19.09.2017
comment
Спасибо за подробное объяснение. Это определенно многое объясняет, я не знал о многих деталях HEAD, индекса и рабочего дерева. Это очень помогло, и я смог определить причину моего, казалось бы, странного поведения из-за того, что мои файлы автоматически добавлялись в индекс при сохранении. - person d.lanza38; 20.09.2017
comment
Я разместил еще один вопрос, потому что Git все еще считает, что файл изменен, несмотря на то, что значения ctime и mtime абсолютно одинаковы. Может быть, вы могли бы посмотреть на вывод и увидеть что-то? @torek stackoverflow.com/questions/46327832/ - person d.lanza38; 20.09.2017
comment
Хм, возможно, вы неправильно интерпретируете мои замечания о mtime и ctime — записи кэша сравниваются со значениями фактического файла, который, если у вас есть команда stat, запустите stat <path> и посмотрите на значения отметки времени изменения и изменения, и также размер файла в байтах. Или используя Python, import os; print os.lstat('...path...'). Если поля статистики в файле отличаются от кэшированных, запись в кэше устарела и файл может быть изменен, поэтому git status должен выполнить дополнительную работу. - person torek; 20.09.2017
comment
(Я также думаю, что стоит рассмотреть вывод git status --short, который показывает, что Git выполняет два сравнения: HEAD с индексом и индекс с рабочим деревом. Если что-то git add выполняет промежуточный файл , копия индекса может иметь хэш, отличный от копии HEAD. Я думаю, это не отображается в вашем другом вопросе - все эти изменения не подготовлены для фиксации, что означает, что они взяты из сравнения индекса и рабочего дерева.) - person torek; 20.09.2017
comment
Я запустил git status -vv и думаю, что нашел причину. Похоже, права доступа к файлу меняются, когда я сохраняю документ в PHPStorm. Changes not staged for commit: diff --git i/app/socrates/ajaxphp/ajaxFUChangeStatus.php w/app/socrates/ajaxphp/ajaxFUChangeStatus.php old mode 100644 new mode 100755 - person d.lanza38; 20.09.2017
comment
Кроме того, я думаю, что теперь я получаю mtime и ctime. При запуске git ls-files --stage он отображает mtime и ctime версии файла в индексе (поэтапно). И Git сравнивает эти значения с локальными файлами mtime и ctime, которые можно получить с помощью системной команды stat. Если это неправильно, пожалуйста, поправьте меня, хотя. Большое спасибо за то, что уделили нам время — определенно многое узнали о Git. - person d.lanza38; 20.09.2017
comment
Это верно (с точки зрения mtime/ctime) - и изменение разрешений интересно (и похоже на ошибку, оно не должно изменять права доступа к файлу само по себе без разрешения :-)). - person torek; 20.09.2017