Почему git blame не следует переименованиям?
$ pwd
/data/mdi2/classes
$ git blame -L22,+1 -- utils.js
99b7a802 mdi2/utils.js (user 2015-03-26 21:54:57 +0200 22) #comment
$ git blame -L22,+1 99b7a802^ -- utils.js
fatal: no such path mdi2/classes/utils.js in 99b7a802^
как вы заметили, файл был в другом каталоге в этом commit
$ git blame -L22,+1 99b7a802^ -- ../utils.js
c5105267 (user 2007-04-10 08:00:20 +0000 22) #comment 2
несмотря на doc
The origin of lines is automatically followed across whole-file renames (currently there is no option to turn
the rename-following off)
вина не следует за переименованиями. Почему?
обновление: короткий ответ:
git blame
выполните переименование, но не для git blame COMMIT^ -- <filename>
но это слишком трудно отслеживать переименования файлов вручную через массу переименований и тонны истории.
Я думаю, это поведение должно быть зафиксировано, чтобы молча следовать переименования для git blame COMMIT^ -- <filename>
. Или, по крайней мере, --follow
должен быть реализован, поэтому я могу:git blame --follow COMMIT^ -- <filename>
обновление 2: это невозможно. Читать ниже.
ОТВЕТ ИЗ MAILLIST по Junio C Hamano
git blame
выполните переименование, но не дляgit blame COMMIT^ -- <filename>
Предположим, у вас есть файл A и файл B в версии v1.0.
через шесть месяцев код был сильно переработан, и вы это делаете не требовать содержимое этих двух файлов отдельно. У вас есть удалены A и B, и многое из того, что у них было, теперь находится в файле C. текущее состояние.
git blame -C HEAD -- C
может следовать содержимое обоих просто отлично, но если вы были позволено сказать
git blame v1.0 -- C
что это значит? C не существует v1.0 вообще. Вы просить следить за содержимым A тогда или B? Как ты скажи, что ты имел в виду а, а не Б, когда говорил, что С в этом. команда?
"git blame" следует за движениями контента и никогда не обрабатывает "переименования" в любой особый способ, так как это глупо, чтобы думать, что переименование каким-то особенным ; -)
то, как вы говорите, какой контент начать копать из команды из его командной строки нужно дать начальную точку commit (по умолчанию HEAD, но вы можете дать COMMIT^ в качестве своего примера) и путь в этом отправная точка. Поскольку нет никакого смысла говорить C Git и тогда волшебным образом сделай это. думаю, ты имел в виду а в некоторых случаях и Б В некоторых другой. Если v1.0 не было C, единственное разумное, что нужно сделать, это выход вместо того, чтобы делать предположение (и не говоря пользователю, как это угаданный.)
2 ответов
git blame
тут следуйте переименованиям (как и git log
если вы даете ему --follow
). Проблема заключается в путь он следует за переименованиями, что является не очень тщательным взломом: когда он отступает по одному фиксации за раз (от каждого ребенка к каждому родителю), он делает diff-тот же самый вид diff, который вы можете сделать вручную с:
git diff -M SHA1^ SHA1
- и проверяет, не обнаружил ли этот diff переименование.1
это все нормально, насколько это происходит, но это значит, что для git blame
чтобы обнаружить переименование, (a) git diff -M
должен быть в состоянии чтобы обнаружить его (к счастью, это так) и-вот что вызывает у вас проблемы-он должен шаг через переименовать.
например, предположим, что график фиксации выглядит примерно так:
A <-- B <-- ... Q <-- R <-- S <-- T
где каждая заглавная буква представляет собой совершить. Предположим далее, что файл был переименован в совершения R
, так что в commits R
через T
у него есть имя newname
в то время как в commits A
через Q
у него есть имя oldname
.
если вы запустите git blame -- newname
последовательность начинается с T
, сравнивает S
и T
, сравнивает R
и S
, и сравнивает Q
и R
. , когда он сравнивает Q
и R
, git blame
обнаруживает изменение имени и начинает искать oldname
в совершает Q
и раньше, поэтому, когда он сравнивает P
и Q
он сравнивает файлы oldname
и oldname
в этих двух коммитов.
если, с другой стороны, вы запустите git blame R^ -- newname
(или git blame Q -- newname
) так, что последовательность начинается с фиксации Q
нет файла newname
в этом фиксации, и нет переименования при сравнении P
и Q
и git blame
просто сдается.
фокус в том, что если вы начинаете с фиксации, в которой файл имел предыдущее имя, вы должны дать git старое имя:
git blame R^ -- oldname
и потом все это снова работать.
1на git diff
документация, вы увидите, что есть контролирует как git diff
обнаруживает переименовывает. The blame
код немного изменяет это (и на самом деле делает два прохода, один с -M
выключен и секунду с -M
включил) и использует свои собственные (разные) -M
опция для несколько разных целей, но в конечном итоге она использует то же самое код.
[редактировать чтобы добавить ответ на комментарий (не подходит как сам комментарий)]:
- это любой инструмент, который может показать мне переименования файлов, такие как: Git renames
SHA date oldname->newname
не совсем, но git diff -M
близко, и может быть достаточно близко.
я не уверен, что вы подразумеваете под "датой SHA" здесь, но git diff -M
позволяет поставить два SHA-1s и сравнивает влево против вправо. Добавить --name-status
чтобы получить только имена файлов и склонностей. Отсюда git diff -M --name-status HEAD oldsha1
мая сообщить, что конвертировать из HEAD
to oldsha1
, git считает, что вы должны R
ename файл и сообщит старое имя как "новое" имя. Например, в самом репозитории git есть файл с именем Documentation/giteveryday.txt
который раньше имел немного другое имя:
$ git diff -M --name-status HEAD 992cb206
M .gitignore
M .mailmap
[...snip...]
M Documentation/diff-options.txt
R097 Documentation/giteveryday.txt Documentation/everyday.txt
D Documentation/everyday.txto
[...]
если это файл, о котором вы заботитесь,вы хорошо. Две проблемы здесь являются:
- поиск SHA1: где
992cb206
откуда? Если у вас уже есть SHA-1, это легко; если нет,git rev-list
является инструментом поиска SHA1; прочитайте его документацию; - и тот факт, что после серии переименований через каждую фиксацию по одной фиксации за раз, как
git blame
делает, может дать совершенно разные ответы, чем сравнивать столь поздно (HEAD
) против гораздо более раннего коммита (992cb206
или любой другой). В этом случае выходит то же самое, но "индекс подобия" здесь составляет 97 из 100. Если бы он был изменен намного больше на некоторых промежуточных этапах, этот индекс сходства мог бы упасть ниже 50% ... однако, если сравнить редакцию просто мало после992cb206
to992cb206
(asgit blame
будет), возможно, индекс сходства между этими двумя файлами может быть выше.
что нужно (и хватает) для для реализации --follow
, так что все команды что использовать git rev-list
внутренне-т. е. большинство команд, которые работают более чем на одной ревизии-может сделать трюк. По пути, было бы неплохо, если бы он работал в другом направлении (в настоящее время --follow
только новее-старше, т. е. отлично работает с git blame
и работает нормально с git log
до тех пор, пока вы не попросите старейшую историю сначала с --reverse
).
последний git имеет интересную команду. Добавьте рядом с конфигурацией:
[alias]
follow= "!sh -c 'git log --topo-order -u -L ,${3:-}:""'" -
вы можете:
$git follow <filename> <linefrom> [<lineto>]
и вы увидите каждую фиксацию, которая изменяет указанные строки в <filename>
.
также вы можете быть заинтересованы в на :
продолжить перечисление истории файла за пределами переименований (работает только для одного файла).
если вы заинтересованы в использовании обнаружения копирования -C
:
обнаружение копий, а также переименования. См. также --find-copies-harder. Если указано n, то оно имеет то же значение, что и для-M.
-C
будут выглядеть разные файлы в одной фиксации. Если вы хотите обнаружить, что код был взят из другого файла, который не был изменен в этой фиксации. Тогда вы должны предоставить .
по соображениям производительности по умолчанию опция-C находит копии только в том случае, если исходный файл копия была изменена в том же наборе. Этот флаг заставляет команду проверять немодифицированные файлы в качестве кандидатов для источника копирования. Это очень дорогая операция для крупных проектов, поэтому используйте ее с осторожностью. Предоставление более одного варианта-C имеет тот же эффект.
UPD
Я улучшаю этот псевдоним:
[alias]
follow = "!bash -c ' \
if [[ == \"/\"* ]]; then \
FILE=; \
else \
FILE=${GIT_PREFIX}; \
fi; \
echo \"git log --topo-order -u -L ,${3:-}:\\"$FILE\\"\"; \
git log --topo-order -u -L ,${3:-}:\"$FILE\"; \
' --"
теперь вы можете отслеживать, как изменяется указанный диапазон строк:
git follow file_name.c 30 35
примечание: к сожалению, git делает не учитывать изменения в рабочем каталоге. Таким образом, если вы делаете локальные изменения в файле, вы должны спрятать его, прежде чем сможете follow
изменения