Истинный ответ сложен и связан с двойственной природой индекса 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