如何在Git历史记录中grep(搜索)提交的代码

我过去某个时候删除了文件或文件中的某些代码。我可以在内容中(而不是在提交消息中)grep吗?

一个非常糟糕的解决方案是grep日志:

git log -p | grep <pattern>

但是,这并不能立即返回提交哈希。我尝试了git grep,但没有成功。

554615 次浏览

那么,您是否正在尝试通过旧版本的代码查看最后存在的位置?

如果我这样做,我可能会使用git二分法。使用二分法,您可以指定一个已知的好版本,一个已知的坏版本,以及一个简单的脚本,该脚本会检查版本是好是坏(在这种情况下,是一个grep来查看您要查找的代码是否存在)。运行它会发现代码何时被删除。

您应该使用#1鹤头(#0)选项。

搜索Foo

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

查看Git历史记录-通过关键字查找丢失的行了解更多。

-S(命名为pickaxe)最初来自#2选项(Git v0.99,2005年5月)。
然后-Spickaxe)在2006年5月使用Git 1.4.0-rc1成为移植到#2


JakubNarbski评论:

  • 这个查找引入或删除#0实例的差异。它通常意味着“您添加或删除带有'Foo'的行的修订”。

  • --pickaxe-regex选项允许您使用扩展的POSIX正则表达式而不是搜索字符串。示例(来自#0):git log -S"frotz\(nitfol" --pickaxe-regex


正如Rob评论的那样,这个搜索是区分大小写的——他打开了一个关于如何搜索不区分大小写的后续问题


嗨天使笔记的评论

执行git log -G<regexp> --branches --all-G-S相同,但对于正则表达式)与接受了一个git grep <regexp> $(git rev-list --all))做同样的事情,但它快得多!

接受的答案仍然是在我运行10分钟后搜索文本,而这个答案在🤷‍♂️4秒后给出结果。这里的输出也更有用

要搜索提交内容(即,源的实际行,而不是提交消息等),您需要执行:

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

这将grep通过regexp的所有提交文本。

在这两个命令中传递路径的原因是因为rev-list将返回对lib/util的所有更改发生的修订列表,但您还需要传递给grep,以便它只在lib/util中搜索。

想象一下下面的场景:grep可能会在rev-list返回的同一版本中包含的其他文件上找到相同的<regexp>(即使该版本上没有对该文件进行更改)。

以下是一些其他有用的搜索源的方法:

搜索文本匹配正则表达式regexp的工作树:

git grep <regexp>

在工作树中搜索匹配正则表达式regex1或regex2的文本行:

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

在工作树中搜索匹配正则表达式regex1和regex2的文本行,仅报告文件路径:

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

在工作树中搜索具有文本匹配正则表达式regex1行和文本匹配正则表达式regex2行的文件:

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

搜索工作树以获取更改的文本匹配模式行:

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

搜索文本匹配正则表达式regexp的所有修订:

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

搜索rev 1和rev 2之间的所有版本以获取文本匹配正则表达式regexp:

git grep <regexp> $(git rev-list <rev1>..<rev2>)

我采用了Jeet的回答并将其改编为Windows(感谢这个答案):

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

请注意,对我来说,出于某种原因,删除此正则表达式的实际提交并未出现在命令的输出中,而是出现在它之前的一个提交中。

我最喜欢的方法是使用git log-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/testindex dddc242..60a8ba6 100644--- a/test+++ b/test@@ -1 +1 @@-hello hello+hello goodbye hello

由于“hello”在文件中出现的次数在此提交之前和之后相同,因此使用-Shello将不匹配。但是,由于匹配hello的行发生了变化,因此将使用-Ghello显示提交。

如果你想浏览代码更改(查看整个历史中给定单词实际更改了什么),请选择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 ;) )

Jeet的回答在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 "(^.*?:)|(:.*)", "" }

搜索任何版本,任何文件(Unix/Linux):

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

对于试图在源树中执行此操作的其他人,UI中没有针对它的直接命令(从版本1.6.21.0开始)。但是,您可以通过打开终端窗口(主工具栏中可用的按钮)并在其中复制/粘贴它们来使用接受答案中指定的命令。

注意:Sourcetree的搜索视图可以部分为您进行文本搜索。按Ctrl+3转到搜索视图(或单击底部可用的搜索选项卡)。从最右边,将搜索类型设置为文件更改,然后键入要搜索的字符串。与上述命令相比,此方法有以下限制:

  1. Sourcetree仅在一个更改的文件中显示包含搜索词的承诺。查找包含搜索文本的确切文件再次是一项手动任务。
  2. 不支持RegEx。

git log可能是搜索所有分支中文本的更有效方法,特别是如果有许多匹配项,并且您希望首先查看更多最近(相关)的更改。

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

这些log命令列出了添加或删除给定搜索字符串/regex的提交,(通常)首先是最近的。-p选项会在添加或删除模式的位置显示相关差异,因此您可以在上下文中查看它。

找到添加您要查找的文本的相关提交(例如,8beeff00d)后,找到包含该提交的分支:

git branch -a --contains 8beeff00d
git rev-list --all | xargs -n 5 git grep EXPRESSION

是对Jeet的解决方案的调整,因此它在搜索时显示结果,而不仅仅是在最后(在大型存储库中可能需要很长时间)。

为了简单起见,我建议使用GUI:Gitk-Git存储库浏览器。它非常灵活

  1. 搜索代码:

    在此处输入图像描述
  2. 搜索文件:

    在此处输入图像描述
  3. 当然,它也支持正则表达式:

    在此处输入图像描述

您可以使用向上/向下箭头浏览结果。

场景:您使用IDE对代码进行了大量清理。问题:IDE清理的比它应该的多,现在你的代码不编译(缺少资源等)

解决方案:

git grep --cached "text_to_find"

它将找到更改“text_to_find”的文件。

您现在可以撤消此更改并编译您的代码。

每当我发现自己在你的地方,我使用以下命令行:

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-它创建按时间顺序排列的提交图。

添加更多已经存在的答案。如果您知道您可能执行此操作的文件:

git log --follow -p -S 'search-string' <file-path>

好吧,两次只是今天我看到人们想要一个更接近的等价物hg grep,这就像git log -pS,但将其输出限制为仅(注释)更改的行。

如果你想快速浏览一下,我想这比寻呼机中的/pattern/更方便。

所以这是一个diff-hunk扫描仪,它接受git log --pretty=%h -p输出并吐出带注释的更改行。把它放在diffmarkup.l中,比如make ~/bin/diffmarkup,然后像这样使用它

git log --pretty=%h -pS pattern | diffmarkup | grep pattern
%option main 8bit nodefault// vim: tw=0%top{#define _GNU_SOURCE 1}%x commitheader%x diffheader%x hunk%%char *afile=0, *bfile=0, *commit=0;int aline,aremain,bline,bremain;int iline=1;
<hunk>\n        ++iline; if ((aremain+bremain)==0) BEGIN diffheader;<*>\n   ++iline;
<INITIAL,commitheader,diffheader>^diff.*        BEGIN diffheader;<INITIAL>.*     BEGIN commitheader; if(commit)free(commit); commit=strdup(yytext);<commitheader>.*
<diffheader>^(deleted|new|index)" ".*   {}<diffheader>^"---".*            if (afile)free(afile); afile=strdup(strchrnul(yytext,'/'));<diffheader>^"+++".*            if (bfile)free(bfile); bfile=strdup(strchrnul(yytext,'/'));<diffheader,hunk>^"@@ ".*       {BEGIN hunk; char *next=yytext+3;#define checkread(format,number) { int span; if ( !sscanf(next,format"%n",&number,&span) ) goto lostinhunkheader; next+=span; }checkread(" -%d",aline); if ( *next == ',' ) checkread(",%d",aremain) else aremain=1;checkread(" +%d",bline); if ( *next == ',' ) checkread(",%d",bremain) else bremain=1;break;lostinhunkheader: fprintf(stderr,"Lost at line %d, can't parse hunk header '%s'.\n",iline,yytext), exit(1);}<diffheader>. yyless(0); BEGIN INITIAL;
<hunk>^"+".*    printf("%s:%s:%d:%c:%s\n",commit,bfile+1,bline++,*yytext,yytext+1); --bremain;<hunk>^"-".*    printf("%s:%s:%d:%c:%s\n",commit,afile+1,aline++,*yytext,yytext+1); --aremain;<hunk>^" ".*    ++aline, ++bline; --aremain; --bremain;<hunk>. fprintf(stderr,"Lost at line %d, Can't parse hunk.\n",iline), exit(1);

在答案https://stackoverflow.com/a/2929502/6041515的启发下,我发现git grep似乎在每次提交时搜索完整的代码库,而不仅仅是差异,结果往往是重复和冗长的。下面的脚本将只搜索每个git提交的差异:

for commit in $(git rev-list --all); do# search only lines starting with + or -if  git show "$commit" | grep "^[+|-].*search-string"; thengit show --no-patch --pretty=format:'%C(yellow)%h %Cred%ad %Cblue%an%Cgreen%d %Creset%s' --date=short $commitfidone

示例输出,底部的git提交是第一个引入我正在搜索的更改的:

csshx$ for commit in $(git rev-list --all); do>     if  git show "$commit" | grep "^[+|-].*As csshX is a command line tool"; then>         git show --no-patch --pretty=format:'%C(yellow)%h %Cred%ad %Cblue%an%Cgreen%d %Creset%s' --date=short $commit>     fi> done
+As csshX is a command line tool, no special installation is needed. It may987eb89 2009-03-04 Gavin Brock Added code from initial release

A.完整、唯一、排序的路径:

# Get all unique filepaths of files matching 'password'# Source: https://stackoverflow.com/a/69714869/10830091git rev-list --all | (while read revision; dogit grep -F --files-with-matches 'password' $revision | cat | sed "s/[^:]*://"done) | sort | uniq

B.唯一、排序的文件名(不是路径):

# Get all unique filenames matching 'password'# Source: https://stackoverflow.com/a/69714869/10830091git rev-list --all | (while read revision; dogit grep -F --files-with-matches 'password' $revision | cat | sed "s/[^:]*://"done) | xargs basename | sort | uniq

第二个命令对BFG很有用,因为它只接受文件名而不接受repo相对/系统绝对路径。

查看我的完整答案在这里以获取更多解释。

我在这里有点惊讶,也许我错过了我正在寻找的答案,但是我来这里是为了搜索所有分支的头部。不是针对存储库中的每个修订,所以对我来说,使用git rev-list --all信息太多了。

换句话说,对我来说,最有用的变化是

git grep -i searchString $(git branch -r)

git branch -r | xargs git grep -i searchString

git branch -r | xargs -n1 -i{} git grep -i searchString {}

当然,您可以在这里尝试正则表达式方法。这里的方法很酷的地方在于它直接针对远程分支工作。我不必检查这些分支中的任何一个。

在git历史中搜索的命令

git log -S"alter" --author="authorname" --since=2021.1.1 --until=2023.1.1 -- .