Ответ 1
eftshift0 ответ правильный (и я проголосовал за него). Вот почему, хотя - наряду с еще одной вещью, которая может пойти не так, как надо здесь.
В большинстве случаев git show commit -- path
будет правильным и покажет вам:
- сообщение журнала для указанного коммита и
- патч для этого конкретного файла, созданный путем сравнения этого коммита с его родителем.
Патч будет таким же, как и у git diff commit^1 commit -- path
. (Обратите внимание, что здесь суффикс ^1
является литеральным текстом, а часть commit
- заменяемым идентификатором хеша. Синтаксис суффикса означает "найти первого родителя". Вы можете добавить этот суффикс для большинства селекторов фиксации, но не для селекторов, которые используют определенные шаблоны поиска. См. документацию gitrevisions.)
Есть два важных исключения. Здесь применимо только одно из них, потому что git blame
обычно не обвиняет слияние, оно пытается отследить источник изменений, внесенных в слияние. Тем не менее, я хочу упомянуть об этом, потому что поведение git show
при слияниях... интересно. :-)
Если вы посмотрите на коммит слияния с помощью git show
, вы увидите, по умолчанию, комбинированный diff ("все родители" против контента коммитов слияния). В этом случае вы можете обратиться к git diff
, чтобы указать родителя, которого хотите сравнить (^1
, ^2
и даже больше, если это слияние осьминога). Причина в том, что комбинированные diff намеренно опускают любой файл, который соответствует версии в одном из родительских коммитов: это означает, что все, что находится в хранилище с этого момента, было получено одним из двух (или N, если N> 2) " стороны "слияния.
С git blame
, вы ищете "кто что изменил". Человек, который сделал слияние, часто не тот, кто внес изменение, поэтому вы должны продолжать идти "за слиянием", чтобы выяснить, кто действительно изменил его.
Второе исключение - это то, что вызвало у вас проблему здесь, и это скорее пример того, как работает git blame
когда файлы переименовываются во время разработки.
Когда git blame
анализирует изменения в файле, такие как include/svl/itemset.hxx
, он делает шаг назад на один коммит за раз, начиная с любого коммита, который вы укажете. Если вы не выбрали свою собственную начальную точку, она начинается с HEAD
, то есть с текущего коммита. Затем он смотрит на родительский коммит (как, например, с помощью git show
). Например, если текущий коммит 922e935c8812
является обычным коммитом, а его родитель - 22c6554c98e2
, он сравнивает коммит 922e935c8812
с 22c6554c98e2
. Если 22c6554c98e2
имеет файл с тем же именем, то, вероятно, тот же файл... но если нет, Git пытается выяснить, какой файл в 22c6554c98e2
является тем же файлом, что и include/svl/itemset.hxx
.
В этом случае именно это происходит при b9337e22ce1d
. Существует файл с именем include/svl/itemset.hxx
в фиксации b9337e22ce1d
, но совершить b9337e22ce1d^
или f4e1642a1761
, файл с именем svl/inc/svl/itemset.hxx
. Git обнаруживает это переименование, когда отступает от коммита b9337e22ce1d
для фиксации f4e1642a1761
, и git blame
переносит новое имя вместе с коммитом f4e1642a1761
для фиксации d210c6ccc3
.
Однако когда вы запускаете git show d210c6ccc3
, Git переходит непосредственно к d210c6ccc3
(и его родительскому 7f0993d43019
). Он больше не знает, что файл с именем include/svl/itemset.hxx
в HEAD
называется svl/inc/svl/itemset.hxx
в d210c6ccc3
. Таким образом, вы должны обнаружить это и передать Git более раннее имя.
Вы можете задаться вопросом, как вы можете найти это. Ответ заключается в использовании git log --follow
. --follow
Код для git log
не велик, но это 1 и тот же код, что git blame
использует, поэтому он производит те же самые ответы, по крайней мере. Вот что я сделал:
$ git log --oneline --follow --name-status include/svl/itemset.hxx
00aa9f622c29 Revert "used std::map in SfxItemSet"
M include/svl/itemset.hxx
afaa10da2572 make SfxItemSet with SAL_WARN_UNUSED
M include/svl/itemset.hxx
[snip]
a7724966ab4f Bin comments that claim to say why some header is included
M include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100 svl/inc/svl/itemset.hxx include/svl/itemset.hxx
Там второе переименование еще раньше. Вот еще один, более короткий способ найти только переименования:
$ git log --oneline --follow --diff-filter=R --name-status include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100 svl/inc/svl/itemset.hxx include/svl/itemset.hxx
e6b4345c7f40 #i103496#: split svtools in two libs, depending on whether the code needs vcl or not
R100 svtools/inc/svtools/itemset.hxx svl/inc/svl/itemset.hxx
Если d210c6ccc3
пришел "до" e6b4345c7f40
, вам пришлось бы использовать более раннее имя пути, но commit d210c6ccc3
является потомком (приходит после) e6b4345c7f40
, а не предком.
1 Когда изменения происходят при слияниях, это действительно, в фундаментальном смысле, требует одновременного выполнения обеих (или всех) входных фиксаций. Тем не менее, Git (в настоящее время?) Не способен сделать это: и git log --follow
и git blame
действительно только git log --follow
родителя окончательная версия файла " git log --follow
". То есть, если мы посмотрим на типичный коммит M
слияния, содержащий объединенный файл F
, есть два входа M^1:F
и M^2:F
и один выход M:F
Если M:F
совпадает с M^1:F
, мы должны полностью игнорировать M^2:F
: все содержимое M:F
принадлежит тем, кто предоставил M^1:F
Если M:F
совпадает с M^2:F
, мы должны полностью игнорировать M^1:F
: все содержимое M:F
принадлежит тем, кто предоставил M^2:F
Обратите внимание, что это работает только для одного файла за раз, и даже тогда, это работает, только если файл точно соответствует одному из двух входных данных. В противном случае, мы должны посмотреть на объединенную разность, чтобы увидеть, как файл был изменен с обоих входов. Это логика, лежащая в основе комбинированных различий и упрощения истории. Но в некоторых редких случаях он слишком упрощен, и, следовательно, иногда что-то не так.