Подробное обсуждение вычисления индекса подобия см. В Попытка понять `git diff` и` git mv` механизм обнаружения переименования . Однако прежде чем сделать это, примите к сведению:
Каждый коммит представляет собой полный автономный снимок. Снимок - это дерево именованных файлов и именованных каталогов (или папок), содержащее больше файлов и / или более каталогов. 1 При наличии коммита и полного имени пути path/to/file.ext
, Git может извлечь соответствующие содержимое BLOB-объекта (как их называет Git), в котором содержится именованный файл внутри этого коммита, без необходимости просматривать другие коммиты.
Каждый раз, когда вы спрашиваете Git о снимке для целей сравнения, вы должны указать Git хэш-идентификаторы или имена или другие строки, которые разрешаются в хэш-идентификаторы, из двух коммитов - двух снимков. Git в действительности извлекает каждый снимок по одному, а затем сравнивает результирующее дерево файлов. (Некоторые команды, такие как git show
и git log -p
, выясняют родительский хеш, просматривая дочерний коммит, затем сравнивают родительский и дочерний в этом порядке.)
Таким образом, Git всегда смотрит на пару деревьев: левое (a/
) дерево может содержать README.txt
, а правое (b/
) также содержит Например, README.txt
, в то время как левая сторона содержит doc.txt
, а правая не имеет doc.txt
. Левый коммит не имеет documentation.rst
, а правый имеет documentation.rst
.
На этом этапе Git сопоставляет файлов. Два файла с одинаковым именем пути (например, два файла README.txt
здесь) должны быть "одним и тем же" файлом, поэтому Git просматривает содержимое левой стороны README.txt
и содержимое правой стороны README.txt
произвести различие этих двух. Технический термин для сопоставления таких вещей определяет идентичность файлов. (Это настоящий подвиг с философской точки зрения. См. Корабль Фесуса для обсуждения. В отличие от философских рассуждений, в вычислительной технике мы получаем четкий и конкретный ответ. Что ж, мы будем поступать, пока не введем такие вещи, как Git's -B
или break значение в git diff
, как минимум!)
Если имена не совпадают, например, doc.txt
против documentation.rst
, Git вычисляет индекс сходства между каждой такой парой файлов, сравнивая файлы левой стороны (которые в этот момент кажется, что удаляются при переходе на правую сторону) к файлам правой стороны (которые сейчас кажутся новыми файлами). То есть Git вычисляет этот индекс , если вы включили обнаружение переименования . Обнаружение переименования отключено по умолчанию в версиях Git до Git версии 2.9 и включено по умолчанию в последующих версиях. Git выбирает здесь лучшие совпадения и объединяет файлы в пару: если doc.txt
достаточно похож на documentation.rst
, почему тогда это также должен быть "один и тот же" файл, даже если они имеют разные имена.
Прежде чем Git даже надумает использовать этот трюк с индексом сходства, он делает первый проход, чтобы найти 100% идентичные файлы. Это намного проще, чем вычисление индекса сходства, благодаря тому, как Git хранит контент. Любые такие точные совпадения объединяются в пару и исключаются из списка файлов, которые потенциально могут быть сопоставлены, оставляя только те файлы, которые не имеют точные совпадения в том, что Git внутренне называет очередь переименования. Таким образом, вычисление индекса сходства выполняется только для файлов, имена которых находятся в очереди переименования. Это вычисление относительно дорого (его число равно O (n 2 ) по количеству файлов), поэтому для быстрых git show
или git log -p
хорошей идеей будет зафиксировать только переименованием сначала, а затем любые изменения в содержании.
1 Это внутреннее представление - снаружи вы даже не должны знать или заботиться о том, чтобы Git сохранял каждый каталог в виде записи дерева. В частности, Git любит утверждать, что он хранит только files (не каталоги), а Git до смешного затрудняет хранение пустой директории. Для этого у Git должно быть пустое дерево - и оно делает , но если вы попытаетесь его использовать, вы получите странные эффекты.