Как мне «свалить вину» на удаленную строку?

506

git blameотлично подходит для измененных и добавленных строк, но как я могу найти, когда строка, которая существовала в конкретном предыдущем коммите, была в конечном итоге удалена. Я думаю bisect, но я надеялся на что-то более удобное.

(Прежде чем вы спросите: в этом случае я просто сделал a git log -pи перебрал строку кода, и (а) какой-то идиот только что удалил жизненно важную строку в предыдущем коммите, и (б) я был этим идиотом.)

Malvolio
источник
4
Есть продолжение с уточняющим ответом, который git log -S<string> /path/to/fileхочет , чтобы -cили -ccво время слияния (конфликты) также отображались удаления
cfi
3
Это должно быть -cи --cc. @Steen: Правильно, спасибо за указание! Глупый недосмотр. Хотел бы я отредактировать комментарий. Добавление нового, затем удаление моего, а затем удаление вашего слишком громоздко, я думаю :)
Cfi
4
Я хотел git blameбы иметь возможность показывать удаленные строки (возможно с зачеркиванием или красным текстом) с ревизией, в которой они были удалены.
Крейг МакКуин
Это было бы трудно написать? Я не знаю много о внутренностях Git.
Мальволио

Ответы:

636

Если вы знаете содержимое строки, это идеальный вариант использования для:

git log -S <string> path/to/file

который показывает вам коммиты, которые вводят или удаляют экземпляр этой строки. Также есть то, -G<regex>что делает то же самое с регулярными выражениями! Смотрите man git-logи поиск -Gи -Sварианты, или кирка (понятное имя для этих функций) для получения дополнительной информации.

Эта -Sопция также упоминается в заголовке git-blameman-страницы, в разделе описания, где приводится пример использования git log -S....

Cascabel
источник
Блестящий ... как раз то, что мне было нужно в этой работе по портированию, над которой я работаю +1
jkp
37
После использования Git в течение более 1 года меня по-прежнему поражает то, что у Git всегда есть команда / опция где-то для решения почти любого сценария использования, который у меня есть. Спасибо, что поделились этим, это именно то, что мне сейчас нужно!
Паскаль Бурк
22
Этот метод работал у меня раньше, но только сейчас я увидел случай, когда он не нашел коммит, где была удалена строка. Оказалось, что рассматриваемая строка была удалена в коммите слияния - это объяснит ошибку? ( git blame --reverseМетод нашел его, хотя.)
antinome
9
@antinome Чтобы показать коммиты от слияния, используйте -cопцию дополнительно.
yunzen
2
Я сделал Ctrl + F на "-s" на странице руководства и ничего не нашел. Где на странице вы это видите ?? Я использую git 1.8.5.2
временное_имя_пользователя
135

Я думаю, что вы действительно хотите,

git blame --reverse START..END filename

Из справочной страницы :

Пройдите историю вперед, а не назад. Вместо того, чтобы показывать ревизию, в которой появилась строка, она показывает последнюю ревизию, в которой существовала строка. Это требует ряда изменений, таких как START..END, где путь к обвинению существует в START.

С помощью git blame reverseвы можете найти последний коммит, в котором появилась строка. Вам все еще нужно получить коммит, который следует после.

Вы можете использовать следующую команду, чтобы показать обратный журнал git. Первый показанный коммит будет в последний раз, когда появляется эта строка, а следующий коммит будет, когда он будет изменен или удален.

git log --reverse --ancestry-path COMMIT^..master
Chronial
источник
14
Если есть несколько слияний из ветви, где линия была добавлена ​​в ветку, где линия отсутствует (или любой другой случай, когда в линии есть несколько путей от START до END), git blame --reverseбудет показана ревизия до слияния, которая была хронологически наконец, не ревизия перед первоначальным слиянием, где было принято решение не принимать строку. Есть ли способ найти самую раннюю ревизию, в которой линия перестала существовать, а не самую последнюю?
Ракслице
2
@rakslice, для этого вы можете использовать вину --reverse --first-parent, это немного лучше.
max630
17

Просто чтобы завершить ответ Каскабель :

git log --full-history -S <string> path/to/file

У меня была та же проблема, что и здесь, но оказалось, что строка отсутствует, потому что коммит слияния из ветви был возвращен, а затем снова слит в него, эффективно удаляя рассматриваемую строку. В --full-historyфлаг предотвращает пропуск этих фиксаций.

estani
источник
9

git blame --reverse может приблизить вас к месту удаления строки. Но это на самом деле не указывает на ревизию, где строка удаляется. Это указывает на последнюю ревизию, где линия присутствовала . Тогда, если следующая ревизия является простым коммитом, вам повезло, и вы получили ревизию удаления. OTOH, если следующая ревизия является коммитом слияния , то все может стать немного диким.

В рамках усилий по созданию difflame я решил эту проблему, поэтому, если у вас уже есть Python, установленный на вашем компьютере, и вы хотите попробовать его, то не ждите больше и дайте мне знать, как он работает.

https://github.com/eantoranz/difflame

eftshift0
источник
0

Для изменений, скрытых в коммитах слияния

Коммиты слияния автоматически скрывают свои изменения из вывода журнала Git. Как кирка, так и обратная вина не нашли изменений. Итак, нужная мне строка была добавлена, а затем удалена, и я хотел найти слияние, которое ее удалило. История файла git log -p -- path/fileтолько показала, что он был добавлен. Вот лучший способ найти его:

git log -p -U9999 -- path/file

Ищите изменения, затем ищите в обратном направлении «^ commit» - первый «^ commit» - это коммит, в котором файл последний раз содержал эту строку. Второй «^ commit» после того, как он исчез. Второй коммит может быть тем, который удалил его. -U9999Призван показать все содержимое файла (после каждый раз , когда файл был изменен), предполагая , что ваши файлы все не более 9999 строк.

Находит любые связанные слияния с помощью грубой силы (различие каждого возможного коммита слияния с его первым родителем, работает с тоннами коммитов)

git log --merges --pretty=format:"git diff %h^...%h | grep target_text" HEAD ^$(git merge-base A B) | sh -v 2>&1 | less

(Я пытался ограничить фильтр ревизий больше, но столкнулся с проблемами и не рекомендую это. Изменения добавления / удаления, которые я искал, были в разных ветках, которые были объединены в разное время, и A ... B не включал когда изменения фактически были объединены с основной линией.)

Покажите дерево Git с этими двумя коммитами (и удалена большая часть сложной истории Git):

git log --graph --oneline A B ^$(git merge-base A B) (A - первый коммит выше, B - второй коммит выше)

Показать историю A и историю B минус история как A, так и B.

Альтернативная версия (кажется, показывает путь более линейно, чем обычное дерево истории Git - однако я предпочитаю обычное дерево истории git):

git log --graph --oneline A...B

Три, а не две точки - три точки означают «r1 r2 - not $ (git merge-base --all r1 r2). Это набор коммитов, которые доступны либо с одного из r1 (слева) или r2 (справа) сторона), но не от обоих. - источник: "Человек Гитревенс"

Кертис Яллоп
источник