1. 概述

在本教程中,我们将学习如何在 Git 提交历史中搜索特定文本模式。重点将放在如何使用 git log 命令,在提交信息(commit message)或提交差异(commit diff)中查找包含特定字符串的提交。

2. 使用 git log 搜索提交信息或差异

在使用 Git 时,我们经常需要查找包含特定文本的提交。这在查找引入 bug 的提交、或者查找某个功能变更的代码时非常有用。

下面是一个 Git 提交的示例:

$ git show 20ec5cf2aaf273eaa941337af1213b8033fab2b5
commit 20ec5cf2aaf273eaa941337af1213b8033fab2b5
Author: Ilotana Ainebab <anebab@example.com>
Date:   Thu Dec 26 13:22:48 2019 +0300

    Simplify nil check for slice

    len() for nil slices is defined as zero (gosimple)

diff --git a/runtime/ui/key/binding.go b/runtime/ui/key/binding.go
index 8697145..5d4605a 100644
--- a/runtime/ui/key/binding.go
+++ b/runtime/ui/key/binding.go
@@ -65,7 +65,7 @@ func NewBindingFromConfig(gui *gocui.Gui, influence string, configKeys []string,
                if err != nil {
                        return nil, err
                }
-               if keys != nil && len(keys) > 0 {
+               if len(keys) > 0 {
                        parsedKeys = keys
                        break
                }

Git 提交对象包含提交哈希、作者、日期等元信息,以及提交信息正文和差异内容(diff)。

我们可以使用 git log 命令来搜索提交信息和 diff 内容

git log 命令默认按时间顺序展示提交日志,从当前分支最新的提交开始,直到最早的一次提交。它还支持一些选项,比如 --grep-S-G,用于根据文本模式过滤提交。

接下来我们将详细介绍这些选项的使用方法。

3. --grep 选项

git log 支持通过 --grep 选项搜索提交信息中包含特定字符串的提交。

例如,我们可以查找提交信息中包含 “Simplify” 的提交:

$ git log --grep Simplify
commit 20ec5cf2aaf273eaa941337af1213b8033fab2b5
Author: Anatoli Babenia <ababenia@example.com>
Date:   Thu Dec 26 13:22:48 2019 +0300

    Simplify nil check for slice

    len() for nil slices is defined as zero (gosimple)

commit 68acfcdd64131693c198bb6a415c301b04f7d128
Merge: 3752d64 1fa41a3
Author: Alex Goodman <agoodman@example.com>
Date:   Sun Jul 21 15:48:25 2019 -0400

    Merge pull request #206 from muesli/linter-fixes

    Simplify code

--grep 接受正则表达式作为参数,因此我们可以使用正则表达式进行更灵活的匹配。

例如,匹配 “Simplify” 或 “Simplified”:

$ git log --grep Simpl\w*
commit 20ec5cf2aaf273eaa941337af1213b8033fab2b5
Author: Anatoli Babenia <ababenia@example.com>
Date:   Thu Dec 26 13:22:48 2019 +0300

    Simplify nil check for slice

    len() for nil slices is defined as zero (gosimple)

commit 68acfcdd64131693c198bb6a415c301b04f7d128
Merge: 3752d64 1fa41a3
Author: Alex Goodman <agoodman@example.com>
Date:   Sun Jul 21 15:48:25 2019 -0400

    Merge pull request #206 from muesli/linter-fixes

    Simplify code

commit 4ab1ce983b5c05a2cfe2efc90201a3181f89c808
Merge: 0ca94f2 04d4881
Author: Alex Goodman <agoodman@example.com>
Date:   Sun Jul 21 15:45:53 2019 -0400

    Merge pull request #202 from muesli/simplified-fixes

    Simplified boolean comparisons

最后一个提交信息中包含 “Simplified”,也匹配了正则表达式 Simpl\w*

3.1. 忽略大小写匹配

git log --grep 支持通过 -i 选项进行大小写不敏感的匹配:

$ git log -i --grep simplified

该命令将匹配所有提交信息中包含 “simplified” 的提交,无论大小写。

3.2. 正则表达式类型

Linux 系统支持多种正则表达式类型:

  • 基本正则表达式(BRE):默认使用
  • 扩展正则表达式(ERE):使用 -E 选项启用
  • Perl 兼容正则表达式(PCRE):使用 -P 选项启用

示例:

# 使用 ERE
$ git log -E --grep (simplified|simplify)

# 使用 PCRE
$ git log -P --grep simplified(?=code)

# 固定字符串匹配
$ git log -F --grep simplif.*

使用 -F 可以禁用正则表达式,仅匹配固定字符串。

3.3. --grep 的常见误解

一个常见的误解是:认为 git log --grep 会搜索所有 git log 输出内容,包括 diff。但其实:

--grep 只搜索提交信息,不搜索 diff 内容
❌ 即使加上 -p 也不会搜索 diff

例如:

$ git log -p
commit 2f477ef3b6db96040e1dd908a958566b4f5f0e77 (HEAD -> master)
Author: mjchi7 <mjchi7@example.com>
Date:   Sun Apr 2 09:59:16 2023 +0800

    initialize repository

    Setting up the project

diff --git a/main.py b/main.py
new file mode 100644
index 0000000..0349a44
--- /dev/null
+++ b/main.py
@@ -0,0 +1 @@
+import json

如果我们尝试用 --grep json 搜索 diff 中的 “json”:

$ git log -p --grep json

⚠️ 该命令不会返回任何结果,因为 “json” 不在提交信息中。

4. Git 的 Pickaxe 功能

如果要搜索 diff 内容,需要使用 Git 的 Pickaxe 功能。

Pickaxe 功能用于查找某个字符串在文件中被添加、删除或移动的提交。它通过检查 diff 来实现匹配。

git log 提供了两个选项来使用 Pickaxe 功能:

  • -S:当字符串的出现次数发生变化时匹配
  • -G:只要 diff 中出现该字符串就匹配

4.1. 字符串出现次数变化 (-S)

-S 选项仅在提交中字符串的出现次数发生变化时才匹配。

假设我们有如下提交历史:

commit 8fe0d4e37bf00f20798e9db8cb383f76f0b72c6e (HEAD -> master)
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:08:34 2023 +0800

    remove import

diff --git a/main.py b/main.py
index d820b59..e69de29 100644
--- a/main.py
+++ b/main.py
@@ -1 +0,0 @@
-import matplotlib.pyplot as plt

commit 909b0a026e4115d6ee29a9fd9eeeee389806eccc
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:08:10 2023 +0800

    modify import line

diff --git a/main.py b/main.py
index 0e92f4a..d820b59 100644
--- a/main.py
+++ b/main.py
@@ -1 +1 @@
-import matplotlib.pyplot
+import matplotlib.pyplot as plt

commit 60d9e1be2d0607d8443f1481aee0c8ad3cb0dda8
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:07:31 2023 +0800

    add new file

diff --git a/main.py b/main.py
new file mode 100644
index 0000000..0e92f4a
--- /dev/null
+++ b/main.py
@@ -0,0 +1 @@
+import matplotlib.pyplot

运行:

$ git log -S matplotlib
commit 8fe0d4e37bf00f20798e9db8cb383f76f0b72c6e (HEAD -> master)
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:08:34 2023 +0800

    remove import

commit 60d9e1be2d0607d8443f1481aee0c8ad3cb0dda8
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:07:31 2023 +0800

    add new file

⚠️ 第二个提交未被匹配,因为其中 “matplotlib” 的出现次数未发生变化。

如果希望将参数作为正则表达式处理,可以加上 --pickaxe-regex

$ git log --pickaxe-regex -S "(matplotlib|mpl)"

4.2. 字符串出现在 diff 中 (-G)

-G-S 不同,只要 diff 中包含指定字符串,就匹配。

继续使用上面的例子:

$ git log -G matplotlib
commit 8fe0d4e37bf00f20798e9db8cb383f76f0b72c6e (HEAD -> master)
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:08:34 2023 +0800

    remove import

commit 909b0a026e4115d6ee29a9fd9eeeee389806eccc
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:08:10 2023 +0800

    modify import line

commit 60d9e1be2d0607d8443f1481aee0c8ad3cb0dda8
Author: mjchi7 <mjchi7@example.com>
Date:   Sat Apr 1 11:07:31 2023 +0800

    add new file

✅ 所有三个提交都被匹配,因为它们的 diff 中都包含 “matplotlib”。

⚠️ -G 默认就将参数视为正则表达式,不需要额外标志。

5. 总结

在本文中,我们学习了如何在 Git 提交历史中搜索字符串:

  • git log --grep:搜索提交信息中的字符串
  • git log -S:搜索 diff 中字符串出现次数变化的提交
  • git log -G:搜索 diff 中出现指定字符串的所有提交

掌握这些命令可以显著提高我们在 Git 仓库中定位变更的效率。


原始标题:Searching for a String in Git Commits