Как выполнить поиск (поиск) зафиксированного кода в истории 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


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


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

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


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. Конечно, он также поддерживает регулярные выражения:

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

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


6

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

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

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

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 - Создает график хронологически упорядоченных коммитов.

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» был изменен.

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


0
git rev-list --all | xargs -n 5 git grep EXPRESSION

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


-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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.