Как выполнить поиск (поиск) зафиксированного кода в истории Git

1435

Я удалил файл или некоторый код в файле когда-то в прошлом. Могу ли я получить доступ к содержимому (не к сообщениям о коммитах)?

Очень плохим решением является поиск в журнале:

git log -p | grep <pattern>

Однако это не сразу возвращает хеш коммита. Я играл git grepбезрезультатно.

Ортвин Генц
источник
2
Эти сообщения в блоге Junio ​​C Hamano (сопровождающий git) могут быть вам интересны: * Ультимативный инструмент отслеживания контента Линуса (о поиске кирки, т.е. git log -Sи вине) * [Забава с "git log --grep"] [2] (поиск сообщений коммитов ) * [Веселье с "git grep"] [3] [2]: gitster.livejournal.com/30195.html [3]: gitster.livejournal.com/27674.html
Якуб Наренбский
4
Возможный дубликат Как выполнить grep git
ответ от возможного дубликата на самом деле работает: stackoverflow.com/a/1340245/492
CAD bloke
проблема в том, что это не дает никакого контекста к изменению .. то есть кто / когда
Sonic Soul

Ответы:

1890

Для поиска содержимого фиксации (т. Е. Фактических строк исходного текста, а не сообщений фиксации и т. П.) Необходимо выполнить:

git grep <regexp> $(git rev-list --all)

git rev-list --all | xargs git grep <expression> будет работать, если вы столкнетесь с ошибкой «Список аргументов слишком длинный».

Если вы хотите ограничить поиск каким-либо поддеревом (например, «lib / util»), вам нужно будет передать это rev-listподкоманде, grepа также:

git grep <regexp> $(git rev-list --all -- lib/util) -- lib/util

Это пролистает весь текст вашего коммита regexp.

Причина передачи пути в обеих командах состоит в том, что rev-listвернет список ревизий, в котором lib/utilпроизошли все изменения , но также вам нужно перейти к нему, grepчтобы он только выполнял поиск lib/util.

Просто представьте следующий сценарий: grepможет найти то же самое <regexp>в других файлах, которые содержатся в той же ревизии, возвращенной rev-list(даже если в этой ревизии не было изменений в этом файле).

Вот несколько других полезных способов поиска вашего источника:

Найдите в рабочем дереве текст, соответствующий регулярному выражению регулярное выражение:

git grep <regexp>

Найдите в рабочем дереве строки текста, соответствующие регулярному выражению regexp1 или regexp2:

git grep -e <regexp1> [--or] -e <regexp2>

Поиск в рабочем дереве строк текста, соответствующих регулярным выражениям regexp1 и regexp2, только пути к файлам отчетов:

git grep -l -e <regexp1> --and -e <regexp2>

Найдите в рабочем дереве файлы, в которых строки текста соответствуют регулярному выражению regexp1, а строки текста соответствуют регулярному выражению regexp2:

git grep -l --all-match -e <regexp1> -e <regexp2>

Поиск рабочего дерева по измененным строкам соответствия текста:

git diff --unified=0 | grep <pattern>

Поиск всех ревизий для текста, соответствующего регулярному выражению regexp:

git grep <regexp> $(git rev-list --all)

Поиск всех ревизий между rev1 и rev2 для текста, соответствующего регулярному выражению regexp:

git grep <regexp> $(git rev-list <rev1>..<rev2>)
Джит
источник
61
Спасибо, отлично работает! Печально, однако, что «$ (git rev-list --all)» необходим, и нет удобного переключателя для указания поиска во всей истории ветки.
Ортвин Генц
3
Отлично. +1. GitBook добавляет некоторые детали ( book.git-scm.com/4_finding_with_git_grep.html ), а Джунио С. Хамано иллюстрирует некоторые из ваших соображений
VonC
18
К сожалению, я не могу добиться этого с msysgit-1.7.4. Это говорит мне sh.exe": /bin/git: Bad file number. Ответ VonC также работает с msysgit.
Eckes
4
Если при вызове git grep history с rev-list вы получаете сообщение об ошибке «неспособно прочитать дерево», возможно, вам придется исправить ситуацию. Попробуйте git gcили проверьте: stackoverflow.com/questions/1507463/…
Энтони Паноззо
8
Да, это, похоже, не работает на Windows, увы.
mlissner
552

Вы должны использовать опцию кирки ( -S)git log .

Для поиска Foo:

git log -SFoo -- path_containing_change
git log -SFoo --since=2009.1.1 --until=2010.1.1 -- path_containing_change

Посмотрите историю Git - найдите потерянную строку по ключевому слову для получения дополнительной информации.


Как прокомментировал Якуб Наребски :

  • это ищет различия, которые вводят или удаляют экземпляр<string> . Обычно это означает "ревизии, в которых вы добавили или удалили строку с 'Foo'".

  • --pickaxe-regexопция позволяет использовать расширенный POSIX регулярное выражение вместо поиска строки. Пример (с git log):git log -S"frotz\(nitfol" --pickaxe-regex


Как прокомментировал Роб , этот поиск чувствителен к регистру - он открыл дополнительный вопрос о том, как искать без учета регистра.

VonC
источник
3
Спасибо, я не знал об этой опции. Похоже, что это лучшее решение, если вы заинтересованы в сообщениях фиксации, а решение Jeet наиболее подходит, если вам нужно традиционное поведение grep в UNIX, состоящее в чистом сопоставлении строк.
Ортвин Генц
@ Ортвин: согласился (и я проголосовал за выбранное решение). git logнемного в вашем вопросе заставил меня путать;)
VonC
12
Объедините это с -pфлагом, чтобы также вывести diff.
Сандер
Есть ли способ исключить все каталоги, соответствующие определенным шаблонам, используя git log -S?
BakaKuna
3
@Anentropic вам понадобятся --branches --allопции для поиска всего репо.
VonC
249

Мой любимый способ сделать это с опцией git log's' -G(добавлено в версии 1.7.4).

-G<regex>
       Look for differences whose added or removed line matches the given <regex>.

Существует небольшая разница между тем, как параметры -Gи -Sопределяют, соответствует ли коммит:

  • Эта -Sопция, по сути, подсчитывает количество совпадений вашего поиска в файле до и после фиксации. Фиксация отображается в журнале, если значения до и после отличаются. Это не будет, например, показывать коммиты, куда была перемещена строка, соответствующая вашему запросу.
  • С помощью этой -Gопции фиксация отображается в журнале, если ваш поиск соответствует любой строке, которая была добавлена, удалена или изменена.

Возьмите этот коммит в качестве примера:

diff --git a/test b/test
index dddc242..60a8ba6 100644
--- a/test
+++ b/test
@@ -1 +1 @@
-hello hello
+hello goodbye hello

Поскольку число раз, когда «hello» появляется в файле, одинаково до и после этой фиксации, оно не будет совпадать с использованием -Shello. Однако, поскольку произошли изменения в сопоставлении строк hello, фиксация будет показана с использованием -Ghello.

Тайлер Холиен
источник
2
Есть ли способ показать соответствующий контекст изменений в выходных данных журнала git?
Тило-Александр Гинкель
13
@ Thilo-AlexanderGinkel - я обычно просто добавляю -pопцию, чтобы показать diff для каждого коммита. Затем, когда журнал открывается в моем пейджере, я ищу все, что ищу. Если ваш пейджер lessи вы git log -Ghello -p, вы можете напечатать /hello, нажать Enterи использовать nи, Nчтобы найти следующее / предыдущее вхождение «привет».
Тайлер Холиен,
Я обнаружил интересную проблему с -GRegex: если в командной строке используется UTF-8, а в файле, который вы просматриваете, используется кодировка ISO-Latin (8 бит), произойдет .*сбой. Например, у меня есть изменение Vierter Entwurf-> Fünfter Entwurf, и, хотя 'V.*ter Entwurf'выдает совпадение, 'F.*ter Entwurf'нет.
У. Уиндл
51

Если вы хотите просмотреть изменения кода (посмотреть, что на самом деле было изменено с данным словом во всей истории), перейдите в patchрежим - я нашел очень полезную комбинацию выполнения:

git log -p
# Hit '/' for search mode.
# Type in the word you are searching.
# If the first search is not relevant, hit 'n' for next (like in Vim ;) )
Бартек Сквира
источник
11
Принятое решение не работает ни для меня, ни для git log -S. Этот сделал!
Rodvlopes
29

git log может быть более эффективным способом поиска текста во всех ветвях, особенно если совпадений много, и вы хотите сначала увидеть более свежие (релевантные) изменения.

git log -p --all -S 'search string'
git log -p --all -G 'match regular expression'

Эти комманды журнала регистрируют коммиты, которые добавляют или удаляют заданную строку поиска / регулярное выражение, (как правило) более новые в первую очередь. -pОпция вызывает соответствующий дифференциал будет показан , где была добавлена или удалена картина, так что вы можете увидеть его в контексте.

Найдя соответствующий коммит, который добавляет искомый текст (например, 8beeff00d), найдите ветки, которые содержат коммит:

git branch -a --contains 8beeff00d
Эдвард Андерсон
источник
Привет, эти строки, кажется, не работают вообще. Моя команда:> git log -p --all -S 'публичная строка DOB {get; набор; } = string.Empty; ' и каждый раз, когда я пытаюсь запустить его, я получаю> fatal: неоднозначный аргумент 'string': неизвестная ревизия или путь вне рабочего дерева. > Используйте '-' для отделения путей от ревизий, например:> 'git <command> [<revision> ...] - [<file> ...]'
user216652
@ user216652 По какой-то причине 'кавычки не группируют строку поиска как один аргумент. Вместо этого 'publicэто аргумент для -S, а остальные обрабатываются как отдельные аргументы. Я не уверен, в какой среде вы работаете, но этот контекст был бы необходим для устранения неполадок. Я бы предложил открыть отдельный вопрос StackOverflow, если это необходимо, чтобы помочь вам устранить неполадки со всем контекстом того, как ваша команда git отправляется в оболочку. Мне кажется, что он отправляется через какую-то другую команду? Комментарии здесь не подходящее место, чтобы понять это.
Эдвард Андерсон
26

Я взял ответ Джита и адаптировал его для Windows (благодаря этому ответу ):

FOR /F %x IN ('"git rev-list --all"') DO @git grep <regex> %x > out.txt

Обратите внимание, что для меня, по какой-то причине, фактический коммит, который удалил это регулярное выражение, не появился в выходных данных команды, а скорее один коммит до него.

ripper234
источник
2
+1 - и если вы хотите избежать нажатия «q» после каждой находки, добавьте --no-pagerв конце команду git
cgp
2
Кроме того, я хотел бы отметить, что добавление к текстовому файлу имеет дополнительное преимущество, заключающееся в отображении соответствующего текста. (добавить в текстовый файл, используя >>results.txtдля тех, кто не разбирается в трубопроводе Windows ...
cgp
1
И я подумал, что синтаксис bash безобразен :)
smido
23

Поиск в любой ревизии, в любом файле :

git rev-list --all | xargs git grep <regexp>

Искать только в некоторых заданных файлах, например, в файлах XML:

git rev-list --all | xargs -I{} git grep <regexp> {} -- "*.xml"

Строки результата должны выглядеть следующим образом: 6988bec26b1503d45eb0b2e8a4364afb87dde7af: bla.xml: текст найденной строки ...

Затем вы можете получить больше информации, такой как автор, дата и разница, используя git show:

git show 6988bec26b1503d45eb0b2e8a4364afb87dde7af
Кристоф Русси
источник
11

Для простоты я бы предложил использовать графический интерфейс: gitk - браузер репозитория Git . Это довольно гибкий

  1. Для поиска кода:

    Введите описание изображения здесь
  2. Для поиска файлов:

    Введите описание изображения здесь
  3. Конечно, он также поддерживает регулярные выражения:

    Введите описание изображения здесь

И вы можете перемещаться по результатам с помощью стрелок вверх / вниз.

watashiSHUN
источник
6

Для тех, кто пытается сделать это в Sourcetree , в интерфейсе пользователя нет прямой команды (начиная с версии 1.6.21.0). Однако вы можете использовать команды, указанные в принятом ответе, открыв окно терминала (кнопка доступна на главной панели инструментов) и скопировав / вставив их в него.

Примечание: представление поиска Sourcetree может частично выполнять поиск текста для вас. Нажмите Ctrl+, 3чтобы перейти к представлению «Поиск» (или нажмите вкладку «Поиск» внизу). В крайнем правом углу установите для параметра «Тип поиска» значение « Изменения файла», а затем введите строку, которую хотите найти. Этот метод имеет следующие ограничения по сравнению с приведенной выше командой:

  1. Sourcetree показывает только коммиты, которые содержат искомое слово в одном из измененных файлов. Поиск точного файла, который содержит текст для поиска, снова является ручной задачей.
  2. RegEx не поддерживается.
Dotnet
источник
4

Всякий раз, когда я оказываюсь у вас, я использую следующую командную строку:

git log -S "<words/phrases i am trying to find>" --all --oneline  --graph

Объяснение:

  1. git log- Нужно ли мне больше писать здесь; он показывает журналы в хронологическом порядке.
  2. -S "<words/phrases i am trying to find>" - Он показывает все те коммиты Git, где любой файл (добавлен / изменен / удален) содержит слова / фразы, которые я пытаюсь найти без символов «<>».
  3. --all - Для обеспечения и поиска по всем филиалам.
  4. --oneline - Он сжимает журнал Git в одну строку.
  5. --graph - Создает график хронологически упорядоченных коммитов.
surajs1n
источник
1
«Всякий раз, когда я оказываюсь у вас, я чувствую необходимость использовать мерзавец!»
Себи
1
Это отличный ответ!
Альф Итон
@ AlfEaton мое удовольствие!
surajs1n
2

Ответ Джита работает в PowerShell.

git grep -n <regex> $(git rev-list --all)

Ниже показаны все файлы в любом коммите, которые содержат password.

# Store intermediate result
$result = git grep -n "password" $(git rev-list --all)

# Display unique file names
$result | select -unique { $_ -replace "(^.*?:)|(:.*)", "" }
Шон Луттин
источник
1

Итак, вы пытаетесь просмотреть старые версии кода, чтобы увидеть, где что-то существует в последний раз?

Если бы я делал это, я бы использовал git bisect . Используя bisect, вы можете указать известную хорошую версию, известную плохую версию и простой скрипт, который проверяет, является ли версия хорошей или плохой (в этом случае grep, чтобы увидеть, присутствует ли код, который вы ищете ). Запуск этого найдет, когда код был удален.

Роб Ди Марко
источник
2
Да, но ваш «тест» может быть скриптом, который ищет код и возвращает «true», если код существует, и «false», если его нет.
Роб Ди Марко
2
Что ж, если код был плохим в 10-й редакции, стал хорошим в 11-й редакции и снова стал плохим в 15-й редакции ...
Paolo
2
Я согласен с Паоло. Двоичный поиск подходит только для «упорядоченных» значений. В случае git bisect это означает, что все «хорошие» ревизии предшествуют всем «плохим» ревизиям, начиная с контрольной точки, но это предположение не может быть сделано при поиске временного кода. Это решение может работать в некоторых случаях, но оно не является хорошим решением общего назначения.
Кент
Я думаю, что это крайне неэффективно, так как все дерево проверяется несколько раз на биссектрису.
У. Уиндл
0

Сценарий. Вы тщательно очистили свой код с помощью IDE. Проблема: IDE очистил больше, чем должен, и теперь ваш код не компилируется (недостающие ресурсы и т. Д.)

Решение:

git grep --cached "text_to_find"

Он найдет файл, в котором «text_to_find» был изменен.

Теперь вы можете отменить это изменение и скомпилировать свой код.

Garytech
источник
0
git rev-list --all | xargs -n 5 git grep EXPRESSION

это настройка решения Jeet , поэтому он показывает результаты во время поиска, а не только в конце (что может занять много времени в большом хранилище).

laktak
источник
-1

В моем случае мне нужно было найти короткий коммит, и перечисленные решения, к сожалению, не работали.

Мне удалось сделать это с помощью (заменить токен REGEX ):

for commit in $(git rev-list --all --abbrev-commit)
do
    if [[ $commit =~ __REGEX__ ]]; then 
        git --no-pager show -s --format='%h %an - %s' $commit
    fi
done
user9869932
источник