У коміті немає запису «файл перейменовано». Tree лише містить ім'я й хеш: у старому дереві є big.txt, у новому - big2.txt. Перейменування Git обчислює заново щоразу, коли показує diff, log чи виконує злиття.
Алгоритм:
- знайти пари «видалений файл» і «доданий файл»;
- точні збіги - однаковий хеш blob-а: це перейменування зі 100% схожістю (
R100). Перевіряється дешево; - для решти - оцінити схожість вмісту кожної пари і вважати перейменуванням, якщо вона вища за поріг. Поріг за замовчуванням - 50%.
git diff -M HEAD~1 --name-status
# R100 big.txt big2.txt
# R087 app/Helpers.php app/Support/Helpers.php
git diff -M80% HEAD~1 # суворіший поріг
git diff -C HEAD~1 # шукати ще й копії
git diff --no-renames HEAD~1 # показати як видалення + додавання
Чому git mv нічого не гарантує: він лише видаляє й додає файл в індексі. Якщо в тому самому коміті файл перейменовано і суттєво змінено, схожість опуститься нижче порогу - і Git покаже видалення одного файлу й створення іншого. Звідси практика: перейменування окремим комітом, зміни вмісту - наступним.
Де це впливає на роботу:
git log --follow fileстежить за історією файлу через перейменування - але лише для одного файлу й саме евристикою;git blameзнаходить рядки, переміщені з інших файлів, з-C;- злиття: якщо в одній гілці файл перейменовано, а в іншій змінено, стратегія
ortзнаходить перейменування і застосовує зміни до нового імені. Невдале визначення дає конфлікт «deleted by us / modified by them»; - ліміт кандидатів: порівняння - квадратична задача. Якщо видалених і доданих файлів забагато, Git пропускає неточне визначення (
diff.renameLimit,merge.renameLimit) і попереджає про це. Після масового переміщення файлів зручно підняти ліміт.
Плюс підходу: перейменування «знаходиться» навіть якщо його зробили без git mv - просто через IDE чи mv - і навіть ретроспективно для старої історії, коли алгоритм покращується з новими версіями Git.