Это «как» состоит из двух частей, которые можно обобщить следующим образом:
Для совершения , Git не заботится . Он просто делает снимки. Какие бы имена файлов вы ни указали Git использовать для хранения содержимого файлов, которое вы указали Git использовать - все они сохраняются в вашем индексе во время запуска git commit
, поэтому вам приходится все время git add
файлы, скопировать их поверх старых версий в индексе - Git просто замораживает все этих файлов с тем, что у них есть в индексе прямо сейчас , когда вы запускаете git commit
. Эти замороженные файлы являются снимком для фиксации.
Другими словами, после того, как вы все переставили с помощью git mv
(который изменяет имена в индексе и рабочем дереве), и обновили содержимое с помощью git add
по мере необходимости, а затем git commit
, вы получите новый снимок с новыми именами и любым обновленным содержимым. Старые моментальные снимки остаются такими же, какими они были: все существующие моментальные снимки замораживаются навсегда или, по крайней мере, до тех пор, пока действует сам коммит. (По умолчанию они живут вечно. Можно удалить коммитов, но это просто, пока вы не распространите их в другие репозитории, после чего они будут продолжать заражать ваш репозиторий от других репозитории, даже если вы удалите их из своих.)
Для сравнения - что включает в себя слияние - Git должен обнаруживать / обнаруживать переименованные файлы. Git делает это, сравнивая файлы content .
Утверждение во втором пункте маркированного списка здесь на самом деле немного преувеличено, но давайте посмотрим, как это работает, проиллюстрировав это с помощью git diff
, который - по крайней мере в том режиме, который нам интересен - сравнивает два коммита. Помните, что каждый коммит представляет собой полный снимок всех файлов. Мы найдем хэш-идентификаторы двух коммитов и запустим:
git diff --find-renames <hash of earlier commit> <hash of later commit>
На этом этапе Git будет извлекать каждый из двух коммитов. («Извлечение» замыкается в максимально возможной степени, что обычно много: Git обычно может проверять замороженные коммиты непосредственно на месте. Но это всего лишь оптимизация скорости; вы можете думать об этом, как Git полностью извлекает оба коммита в временная рабочая область.) Давайте назовем ранний коммит старый , а более поздний новый здесь. 1 Работа git diff
состоит в том, чтобы сообщить вам что нужно сделать, чтобы заменить старое на новое . Это не обязательно то, что делал любой человек, который сделал изменение, просто какой-то набор инструкций, который даст тот же результат .
Чтобы найти эти инструкции, Git будет:
Сначала найдите все файлы с одинаковыми именами в old и new . Git предполагает, что если old имеет файл с именем README
и new имеет файл с именем README
, это должен быть "тот же" файл. Эти файлы объединены в пару: сейчас они выведены из уравнения. Git еще не понял, что делать, чтобы изменить парные файлы, он просто спарил их.
(Есть шаг, который вы можете вставить здесь, используя опцию -B
, для команд, у которых он есть. Но мы пока проигнорируем его, поскольку это только усложняет.)
Теперь, если есть непарные файлы, они представляют собой файлы, которые пропали без вести из старых и / или обнаружились на ровном месте в новых ... или они? Может быть, это файлы, которые были переименованы , и имели некоторое имя O в old и другое имя N в new . Здесь Git вычисляет число индекса сходства для каждой возможной пары файлов.
В целях скорости Git может очень быстро объединить любые два файла (один из старых , один из новых ), которые на 100% идентичны. Это обычно значительно сокращает пул файлов, которые нужно сравнивать сложным способом.
Finally, Git - это непарные файлы, которые он должен рассмотреть в паре, даже если они не идентичны на 100%. Git теперь выполняет полное вычисление индекса сходства (используя тот же xdelta-подобный код, который Git использует для выполнения дельта-сжатия в пакетных файлах) для каждой пары файлов. (Для этого требуется действительно извлечь данные всех этих файлов.) Какие бы файлы ни получили лучший показатель спаривания, они соединяются вместе, если значения превышают выбранный вами минимум, который по умолчанию равен "50% аналогичный".
Файлы, которые остаются непарными после всей этой дополнительной работы, либо просто удаляются, либо создаются заново. (Здесь добавлено еще несколько сложностей, если вы добавите --find-copies
или --find-copies-harder
, но, опять же, мы их здесь проигнорируем.)
Теперь, когда файлы были спарены, то есть теперь, когда git diff
знает, что, скажем, файл README.md
в old в основном соответствует файлу README.rst
в new так, чтобы эти два файла действительно были одним файлом с одним идентификатором, а не двумя разными файлами с двумя идентификаторами - сейчас Git сравнивает каждый парный файл для получения инструкций:
-
: удалить эту строку из версии файла, найденной в old
+
: добавьте эту строку к версии файла, найденной в old
Если вы будете следовать всем инструкциям, включая любую инструкцию «переименовать этот файл», приведенную вверху, это изменит файл, найденный в old , на файл, найденный в new .
1 Вы можете, если хотите, поменять хэш-идентификаторы. Затем Git расскажет вам, как превратить новый коммит в старый коммит.
Как git merge
использует git diff
При объединении двух коммитов Git использует граф коммитов - Направленный ациклический граф, сформированный путем просмотра родительского хэша или хэшей каждого коммита, чтобы соединить все отдельные коммиты в один большой DAG - чтобы найти лучший общий предок совершать. Этот коммит является базой слияния двух указанных коммитов.
Команда git merge
, по сути, запускает две git diff
команды. Оба имеют включенный --find-renames
, порог сходства по умолчанию равен 50%. Вы можете использовать -X find-renames=<number>
, чтобы изменить этот порог, чтобы разрешить большее или меньшее количество пар файлов, имена которых не совпадают.
Два различия:
git diff --find-renames <hash of merge base> <hash of HEAD commit>
и
git diff --find-renames <hash of merge base> <hash of other commit>
Оба дифференциала выполняют вычисления подобия по мере необходимости для любых непарных имен файлов во время внутреннего git diff
.
Осложнения
Добавление -B
говорит Git о break автоматически сопряженных файлах: только то, что файл с именем README
в обоих коммитах не означает, что это действительно то же самое файл. Что, если, например, вы переименовали README
в old/README
, а затем переименовали new/README
в README
? В этом случае Git выполнит вычисление подобия для автоматически спаренных файлов на этом шаге, который я отметил ранее. Если сходство слишком низкое , Git прервет соединение. Позже, если сходство не слишком крайне низкое, и спаривание остается нарушенным, Git объединит два файла, поэтому -B
принимает два числа, а не только одно.
Команда слияния не позволяет указывать аргумент -B
. (Возможно, так и должно быть.)
Если вы используете --find-copies
или --find-copies-harder
, Git просматривает некоторые или все исходные («старые») файлы, чтобы увидеть, скопирован ли с него только что созданный конечный («новый») файл. Они используют один и тот же индекс сходства. Этот шаг выполняется после обнаружения переименования и иногда рассматривает измененные файлы как возможные источники, опять же, потому что это дорого в вычислительном отношении.
Команда слияния также не позволяет указывать параметры поиска-копий.