Попытка понять `git diff` и`git mv` механизм обнаружения переименования

Это продолжение другого вопроса, который я задавал раньше.

Перед редактированием изначально созданный файл something переименовывается в somethingelse что можно наблюдать здесь:

git mv something somethingelse

Файл somethingelse затем переименовывается обратно в something перед вторым редактированием vim:

git mv somethingelse something

В основном в следующей части кода:

# If you add something to the first line, the rename will not be detected by Git
# However, if you instead create 2 newlines and fill line 3 with new code,
# the rename gets detected for whatever reason
printf "\nCOMMAND: vim something\n\n"
vim something

Если в этот момент я добавлю abc к коду, мы бы в конечном итоге:

First line of code. abc

Я думаю, что это добавление 4 байтов в строке 1, что, в свою очередь, приведет к следующему:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   something
        deleted:    somethingelse

Затем, если мы добавим новую строку и введем abc в третью строку (которая также должна быть 4 байта, исправьте меня, если ошибаюсь):

First line of code.

abc

Внезапно Git обнаружит переименование (включая редактирование):

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    somethingelse -> something

Один хороший ответ / комментарий @torek, данный здесь, объясняет это в определенной степени, принимая git diff переименовать порог обнаружения git status во внимание.

Разве Git не должен вести себя одинаково, так как мы добавили 4 байта в обоих случаях, но по-разному, или новая строка имеет какое-то отношение к этому?

1 ответ

Решение

Насколько мне известно, вычисление Git "индекса сходства" не документировано нигде, кроме как в исходном коде, начиная с diffcore-delta.c.

Чтобы вычислить индекс сходства для двух файлов S (источник) и D (место назначения), Git:

  • читает оба файла
  • вычисляет хеш-таблицу всех кусков файла S
  • вычисляет вторую хеш-таблицу всех кусков файла D

Записи в этих двух хеш-таблицах - это просто количество вхождений экземпляров этого хеш-значения (плюс, как отмечено ниже, длина фрагмента).

Значение хеш-функции для файлового блока вычисляется по формуле:

  • начать с текущего смещения файла (изначально ноль)
  • читать 64 байта или до '\n' характер, в зависимости от того, что произойдет первым
  • если файл считается текстовым и существует '\r' перед '\n'Откажитесь от '\r'
  • хэшируйте полученную строку размером до 64 байтов, используя алгоритм, показанный в связанном файле

Теперь, когда есть хеш-таблицы для S и D, каждый возможный хеш hi появляется nS раз в S и nD в D (любой из них может быть нулевым, хотя код пропускает сразу оба значения хеша с нулевым значением). Если количество вхождений в D меньше или равно количеству вхождений в S - т.е. nD ≤ nS - то D "копирует из S" nD раз. Если число вхождений в D превышает число в S (в том числе, когда число в S равно нулю), то D имеет "буквальное добавление" из nD - nS вхождений хешированного фрагмента, и D также копирует все nS оригинальные происшествия, а также.

Каждый хеш-блок сохраняет количество входных байтов, и они умножают количество копий или количество добавлений "кусков", чтобы получить количество скопированных или добавленных байтов. (Удаления, где в D отсутствуют элементы, существующие в S, здесь имеют только косвенный эффект: количество копий и добавлений в байтах уменьшается, но Git специально не считает сами удаления.)

Эти два значения (src_copied а также literal_added) вычислено в diffcore_count_changes переданы в эксплуатацию estimate_similarity вdiffcore-rename.c, Это полностью игнорирует literal_added count (этот счетчик используется при принятии решения о том, как создавать дельты пакетов, но не с точки зрения оценки переименования). Вместо этого только src_copied номер имеет значение:

score = (int)(src_copied * MAX_SCORE / max_size);

где max_size это размер в байтах большего из двух входных файлов S и D.

Обратите внимание, что есть более ранние вычисления:

max_size = ((src->size > dst->size) ? src->size : dst->size);
base_size = ((src->size < dst->size) ? src->size : dst->size);
delta_size = max_size - base_size;

и если два файла изменили размер "слишком сильно":

if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
        return 0;

мы никогда не попадаем в diffcore-delta.c код для их хеширования. minimum_score вот аргумент -M или же --find-renames, преобразованный в масштабированное число. MAX_SCORE является 60000.0 (тип double), поэтому по умолчанию minimum_score, когда вы используете по умолчанию -M50%составляет 30000 (половина из 60000). Однако, за исключением случая приема пищи CR-before-LF, этот конкретный ярлык не должен влиять на результат более дорогого вычисления подобия.

Снова, git status всегда использует значение по умолчанию. Нет ручки для изменения порога (ни количества файлов, разрешенных в очереди поиска переименования). Если бы здесь был код, перейдите сюда, установив rename_score поле параметров сравнения.

Другие вопросы по тегам