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

Posted

技术标签:

【中文标题】如何在 Git 历史记录中 grep(搜索)已提交的代码【英文标题】:How to grep (search) committed code in the Git history 【发布时间】:2011-02-25 02:16:26 【问题描述】:

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

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

git log -p | grep <pattern>

但是,这不会立即返回提交哈希。我玩了git grep 无济于事。

【问题讨论】:

Junio C Hamano(git 维护者)的这些博文可能会让你感兴趣:* Linus's ultimate content tracking tool(关于镐搜索,即 git log -S 和责备)* [有趣的“git log --grep "][2] (搜索提交信息) * [有趣的“git grep”][3] [2]: gitster.livejournal.com/30195.html [3]: gitster.livejournal.com/27674.html How to grep git commits for a certain word的可能重复 可能重复的答案实际上有效:***.com/a/1340245/492 这个问题是它没有为更改提供任何上下文..即谁/何时 我相信,截至 2021 年,VonC's answer 是唯一一个完全正确的人,值得一个绿色的复选标记。 【参考方案1】:

我在这里有点惊讶,也许我错过了我正在寻找的答案,但我来到这里是为了寻找所有分支的头部。不是针对存储库中的每个修订版,所以对我来说,使用 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 

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

【讨论】:

【参考方案2】:

任何修订、任何文件(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

【讨论】:

【参考方案3】:

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

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

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

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

第二个命令对 BFG 很有用,因为它只接受文件名而不接受 repo-relative/system-absolute 路径。

查看我的full answer here 了解更多说明。

【讨论】:

【参考方案4】:

受到https://***.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"; then 
        git show --no-patch --pretty=format:'%C(yellow)%h %Cred%ad %Cblue%an%Cgreen%d %Creset%s' --date=short $commit
    fi  
done

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

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 may
987eb89 2009-03-04 Gavin Brock Added code from initial release

【讨论】:

【参考方案5】:

好的,今天两次我看到人们想要更接近 hg grep 的等价物,它类似于 git log -pS,但将其输出限制为(带注释的)更改的行。 p>

如果您想快速浏览一下,我想这会比寻呼机中的/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);

【讨论】:

【参考方案6】:

为已经存在的答案添加更多内容。 如果您知道您可能制作的文件,请执行以下操作:

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

--follow: 列出文件的历史记录

【讨论】:

【参考方案7】:

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 'public string DOB get;放; = string.Empty;'每次我尝试运行它时,我都会得到 > 致命:不明确的参数“字符串”:未知修订或路径不在工作树中。 > 使用 '--' 将路径与修订分开,如下所示: > 'git [...] -- [...]' @user216652 出于某种原因,' 引号不会将您的搜索字符串组合在一起作为单个参数。相反,'public-S 的参数,并将其余部分视为单独的参数。我不确定您在什么环境中运行,但该上下文对于帮助进行故障排除是必要的。如果需要帮助您进行故障排除,我建议打开一个单独的 *** 问题,其中包含 git 命令如何发送到 shell 的所有上下文。在我看来,它是通过其他命令发送的?这里的评论不是解决这个问题的正确地方。【参考方案8】:
git rev-list --all | xargs -n 5 git grep EXPRESSION

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

【讨论】:

它通过运行git grep 一次在 5 个修订版上给出“实时”结果,供任何好奇的人使用。【参考方案9】:

Jeet's answer 在 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 "(^.*?:)|(:.*)", "" 

【讨论】:

我喜欢你的回答,并且可以看到它的发展方向,但它不适用于 MacOS zsh: parse error near `-unique'` 好的!我得到它的工作***.com/a/69714869/10830091 GOT I HATE BASH【参考方案10】:

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

【讨论】:

接受的解决方案对我也不起作用 git log -S。这个做到了! 我觉得这种交互方式效率最高。但是发现一个事件后如何获取提交 ID? @CristianTraìna 向上滚动,您应该会看到“commit SHA1”【参考方案11】:

我采用了Jeet's answer 并将其改编为 Windows(感谢this answer):

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

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

【讨论】:

+1 -- 如果你想避免在每次查找后点击“q”,请在最后的 git 命令中添加--no-pager 另外,我会注意到附加到文本文件具有实际显示匹配文本的额外优势。 (对于那些不熟悉 Windows 管道的人,使用 &gt;&gt;results.txt 附加到一个文本文件... 我认为 bash 的语法很丑 :)【参考方案12】:

您应该使用git log 的pickaxe (-S) 选项。

搜索Foo

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

更多信息请参见Git history - find lost line by keyword。


正如Jakub Narębski 评论的那样:

查找引入或删除&lt;string&gt; 实例的差异。 它通常表示“您添加或删除带有 'Foo' 的行的修订”。

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


正如Rob 评论的那样,此搜索区分大小写 - 他打开了follow-up question 以了解如何搜索不区分大小写。

【讨论】:

谢谢,我不知道这个选项。如果您对提交消息感兴趣,看起来这是最好的解决方案,如果您需要纯行匹配的传统 UNIX grep 行为,Jeet 的解决方案是最合适的。 @Ortwin:同意(并且我对所选的解决方案投了赞成票)。您问题中的git log 位让我感到困惑;) 将它与-p 标志结合起来也可以输出差异。 有没有办法使用 git log -S 排除所有匹配特定模式的目录? @Anentropic 你需要--branches --all 选项来搜索所有的repo。【参考方案13】:

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

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

如果您遇到“参数列表太长”错误,git rev-list --all | xargs git grep &lt;expression&gt; 将起作用。

如果您想将搜索限制在某个子树(例如,“lib/util”),您需要将其传递给 rev-list 子命令和 grep

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

这将遍历regexp 的所有提交文本。

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

想象一下以下场景:grep 可能会在包含在rev-list 返回的同一修订版中的其他文件上找到相同的&lt;regexp&gt;(即使该修订版上的该文件没有更改)。

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

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

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>

在所有版本中搜索匹配正则表达式正则表达式的文本:

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

搜索 rev1 和 rev2 之间的所有修订以查找匹配正则表达式 regexp 的文本:

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

【讨论】:

谢谢,效果很好!遗憾的是,需要“$(git rev-list --all)”,并且没有方便的开关来指定在整个分支历史中搜索。 优秀。 +1。 GitBook 添加了一些细节 (book.git-scm.com/4_finding_with_git_grep.html),Junio C Hamano 说明了你的一些观点:gitster.livejournal.com/27674.html 不幸的是,我无法使用 msysgit-1.7.4 进行此操作。它告诉我sh.exe": /bin/git: Bad file number。 VonC 的答案也适用于 msysgit。 如果使用 rev-list 调用 git grep history 时出现“无法读取树”错误,则可能需要清理。试试git gc 或查看:***.com/questions/1507463/… 是的,这似乎在 Windows 上也失败了,唉。【参考方案14】:

每当我发现自己在您的位置时,我都会使用以下命令行:

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

解释:

    git log - 我需要在这里写更多;它按时间顺序显示日志。 -S "&lt;words/phrases i am trying to find&gt;" - 它显示了所有那些 Git 提交,其中任何文件(添加/修改/删除)包含我试图找到的没有“”符号的单词/短语。 --all - 在所有分支中强制执行和搜索。 --oneline - 将 Git 日志压缩为一行。 --graph - 它创建按时间顺序提交的图表。

【讨论】:

“每当我发现自己在你的地方,我就觉得有必要使用 git!”【参考方案15】:

场景:您使用 IDE 对代码进行了大量清理。 问题:IDE 清理的超出了应有的范围,现在您的代码无法编译(缺少资源等)

解决方案:

git grep --cached "text_to_find"

它会找到“text_to_find”被改变的文件。

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

【讨论】:

【参考方案16】:

为简单起见,我建议使用 GUI:gitk - The Git repository browser。很灵活

    搜索代码: 要搜索文件: 当然也支持正则表达式:

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

【讨论】:

【参考方案17】:

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

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

    Sourcetree 仅显示其中一个已更改文件中包含搜索词的提交。查找包含搜索文本的确切文件又是一项手动任务。 不支持正则表达式。

【讨论】:

【参考方案18】:

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

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

【讨论】:

有没有办法在 git 日志输出中显示匹配的更改上下文? @Thilo-AlexanderGinkel - 我通常只添加 -p 选项来显示每个提交的差异。然后,当在我的寻呼机中打开日志时,我会搜索我正在寻找的任何内容。如果您的寻呼机是less,而您是git log -Ghello -p,则可以键入/hello,按Enter,然后使用nN 查找下一个/上一个出现的“hello”。 我发现-G 和正则表达式的一个有趣问题:如果命令行使用 UTF-8 并且您正在查看的文件使用一些 ISO-Latin(8 位)编码,.* 会失败。例如,我有一个更改 Vierter Entwurf -> Fünfter Entwurf,而 'V.*ter Entwurf' 产生匹配,'F.*ter Entwurf' 没有。【参考方案19】:

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

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

【讨论】:

是的,但是您的“测试”可以是一个脚本,它可以搜索代码,如果代码存在则返回“true”,如果不存在则返回“false”。 好吧,如果代码在第 10 版中不好,在第 11 版中变好,在第 15 版中再次变坏…… 我同意保罗的观点。二进制搜索仅适用于“有序”值。在 git bisect 的情况下,这意味着所有“好”的修订都在所有“坏”的修订之前,从参考点开始,但是在寻找临时代码时不能做出这种假设。此解决方案在某些情况下可能有效,但它不是一个好的通用解决方案。 我认为这是非常低效的,因为整棵树被多次检查以进行二等分。

以上是关于如何在 Git 历史记录中 grep(搜索)已提交的代码的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Git 历史记录中永久删除提交?

如何在 git 分支中获取给定作者的提交消息历史记录? [复制]

在所有 Git 历史记录中搜索字符串 [重复]

SVN和GIT提交历史记录和代码差异

如何拆分隐藏在历史中的 Git 提交?

如何拆分隐藏在历史中的 Git 提交?