Почему 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 считает, что вы должны Rename файл и сообщит старое имя как "новое" имя. Например, в самом репозитории 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 to 992cb206 (as git 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 изменения