Что такое git эвристика для назначения изменений содержимого в пути к файлам?
Краткая версия:
меньше, чем исходный код git
, где я могу найти полное описание эвристик, которые git
использует для связывания фрагментов контента с определенными отслеживаемыми дорожками?
Подробная версия:
В демонстрационном взаимодействии оболочки Unix) ниже, два файла a
и b
: "git-commit
'ted", затем они изменяются так, чтобы (эффективно) передавать большую часть контента a
до b
, и, наконец, два файла снова совершаются.
Ключевое значение, которое нужно искать, состоит в том, что вывод второго git commit
заканчивается строкой
rename a => b (99%)
, даже если не было переименования файлов (в обычном смысле) (!?!).
Прежде чем показывать демонстрацию, это краткое описание упростит работу.
Содержимое файлов a
и b
генерируется путем объединения содержимого трех вспомогательных файлов ../A
, ../B
и ../C
. Символьно, что состояния a
и b
могут быть представлены как
../A + ../C -> a
../B -> b
перед первым фиксацией и
../A -> a
../B + ../C -> b
прямо перед вторым.
ОК, здесь демо.
Сначала мы показываем содержимое вспомогательных файлов ../A
, ../B
и ../C
:
head ../A ../B ../C
# ==> ../A <==
# ...
#
# ==> ../B <==
# ###
#
# ==> ../C <==
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
(Строки, начинающиеся с #
соответствуют выходу на терминал, фактические выходные линии не имеют ведущего #
.)
Затем мы создаем файлы a
и b
, отображаем их содержимое и фиксируем их
cat ../A ../C > a
cat ../B > b
head a b
# ==> a <==
# ...
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
#
# ==> b <==
# ###
git add a b
git commit --allow-empty-message -m ''
# [master (root-commit) 3576df7]
# 2 files changed, 8 insertions(+)
# create mode 100644 a
# create mode 100644 b
Затем мы изменяем файлы a
и b
и отображаем их новое содержимое:
cat ../A > a
cat ../B ../C > b
head a b
# ==> a <==
# ...
#
# ==> b <==
# ###
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
Наконец, мы фиксируем измененные a
и b
; обратите внимание на вывод git commit
:
git add a b
git commit --allow-empty-message -m ''
# [master 25b806f]
# 2 files changed, 2 insertions(+), 8 deletions(-)
# rewrite a (99%)
# rename a => b (99%)
Я рационализирую это поведение следующим образом.
Как я понимаю, git
рассматривает информацию о структуре каталогов (например, имена файлов отслеживаемых файлов) в качестве дополнительной информации или метаданных, если вы хотите, чтобы быть связанными с основной информацией, которую она отслеживает, а именно различными куски контента.
Поскольку как содержимое, так и имена (в том числе пути) файлов могут меняться между коммитами, git
должен использовать эвристику для связывания имен путей с кусками содержимого. Но эвристика по самой своей природе не гарантируется в 100% случаев. Провал такой эвристики здесь имеет форму истории, которая точно не отражает то, что на самом деле произошло (например, оно сообщает о переименовании файла, даже если файл не был переименован в обычном смысле).
Следующее подтверждение этой интерпретации (а именно, что некоторые эвристики находятся в игре) заключается в том, что AFAICT, если размер переданного фрагмента недостаточно велик, вывод git commit
не будет включать строки rewrite/rename
, (Я включаю демонстрацию этого случая в конце этого сообщения, FWIW.)
Мой вопрос заключается в следующем: не хватает исходного кода git
, где я могу найти полное описание эвристик, которые git
использует для связывания фрагментов контента с определенными отслеживаемыми дорожками?
Эта вторая демонстрация идентична первой во всех отношениях, за исключением того, что вспомогательный файл ../C
- это одна строка короче, чем раньше.
head ../A ../B ../C
# ==> ../A <==
# ...
#
# ==> ../B <==
# ###
#
# ==> ../C <==
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
cat ../A ../C > a
cat ../B > b
head a b
# ==> a <==
# ...
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
#
# ==> b <==
# ###
git add .
git commit -a --allow-empty-message -m ''
# [master (root-commit) a06a689]
# 2 files changed, 7 insertions(+)
# create mode 100644 a
# create mode 100644 b
cat ../A > a
cat ../B ../C > b
head a b
# ==> a <==
# ...
#
# ==> b <==
# ###
# =================================================================
# =================================================================
# =================================================================
# =================================================================
# =================================================================
git add .
git commit -a --allow-empty-message -m ''
# [master 87415a1]
# 2 files changed, 5 insertions(+), 5 deletions(-)
Ответы
Ответ 1
Как вы заметили, Git выполняет обнаружение переименования с использованием эвристики, вместо того, чтобы сказать, что произошло переименование. Команда git mv
, фактически, просто выполняет этап добавления нового пути к файлу и удаления старого пути к файлу. Таким образом, обнаружение переименования выполняется путем сравнения содержимого добавленных файлов с ранее зафиксированным содержимым удаленных файлов.
Сначала собираются кандидаты. Любые новые файлы можно переименовывать цели, и любые удаленные файлы можно переименовать. Кроме того, переписывающие изменения прерываются таким образом, что файл, который более чем на 50% отличается от предыдущей версии, является как возможным источником переименования, так и возможной целью переименования.
Далее будут обнаружены идентичные переименования. Если вы переименуете файл без внесения каких-либо изменений, то файл будет хэш тождественно. Их можно обнаружить, просто выполнив сравнение хэша в индексе без чтения содержимого файла, поэтому удаление этих из списка кандидатов уменьшит количество сравнений, которые вам нужно выполнить.
Наконец, выполняется сравнение подобия. Каждая строка в каждом файле-кандидате хэшируется и собирается в отсортированном списке. Длинные линии разделяются на 60 символов. Простые пробелы могут быть разделены на предположение, что они не вносят большой вклад в соответствие подобия. Линейные хэши из каждого источника-кандидата сравниваются с хешами строк из каждой целевой цели. Если два списка похожи на 60%, они считаются переименованием.
Ответ 2
... не хватает исходного кода git, где я могу найти полное описание эвристик, которые git использует для связывания фрагментов контента с определенными отслеживаемыми дорожками?
В зависимости от того, что вы подразумеваете под "полным", я не думаю, что вы можете найти такое. (В частности, как рассчитываются "проценты"? Является ли это строками, символами/байтами или чем-то еще? Делает ли слово-ориентированный diff что-то менять?) Но магия все внутри git diff
, где она вычисляется динамически каждый раз, когда должен отображаться diff; и эвристика имеет несколько регуляторов, которые дают сильные подсказки:
--no-renames
Отключить обнаружение переименования, даже если файл конфигурации по умолчанию для этого.
-B[<n>][/<m>], --break-rewrites[=[<n>][/<m>]]
Перерыв завершите переписывание изменений в пары delete и create. Это служит двум целям:
-
Это влияет на изменение, которое равно общей перезаписи файл не как серию удаления и вставки, смешанных вместе с очень мало строк, которые соответствуют текстуально как контекст, но как единое удаление всего старого, за которым следует один вставка всего нового, а число m контролирует этот аспект опции -B (по умолчанию 60%). -B/70%
указывает, что меньше чем 30% оригинала должны оставаться в результате для git рассмотрите его как полную переписывание (т.е. в противном случае полученный патч будет серией удаления и вставки, смешанной с контекстные строки).
-
При использовании с -M
полностью перезаписанный файл также рассматривается как источник переименования (обычно -M
рассматривает только файл, который исчез как источник переименования), а число n элементов управления этот аспект опции -B (по умолчанию - 50%). -B20%
указывает что изменение с добавлением и удалением по сравнению с 20% или более размер файла может быть выбран в качестве возможного источник переименования в другой файл.
и т.д.; см. документацию для git -diff.