任何版本控制系统的目的都是记录对代码的变更。这使您能够返回项目历史记录,看看谁贡献了什么,找出错误是在哪里引入的,并恢复有问题的变更。但是,如果您不知道如何浏览,那么拥有这些历史记录也没有用。这就是 git log 命令的用武之地。

到目前为止,您应该已经知道用于显示提交的基本 git log 命令了。但是,您可以通过向 git log 传递许多不同的参数来改变这个输出。

git log 的高级功能可以分为两类:格式化每个提交的显示方式,以及筛选输出中包含哪些提交。这两种技能相结合,让您有能力返回您的项目,找到您可能需要的任何信息。

格式化日志输出

首先,本文将介绍格式化 git log 输出的多种方式。其中大多数以标记的形式出现,允许您从 git log 请求更多或更少的信息。

如果您不喜欢默认的 git log 格式,可以使用 git config 别名功能为下面讨论的任何格式化选项创建快捷方式。有关如何设置别名,请参阅 git config 命令

Oneline

--oneline 标记将每个提交压缩为一行。默认情况下,它只显示提交 ID 和提交消息的第一行。典型的 git log --oneline 输出将类似于下方的内容:

0e25143 Merge branch 'feature'
ad8621a Fix a bug in the feature
16b36c6 Add a new feature
23ad9ad Add the initial code base

这对于获得项目的高级概述非常有用。

装饰

很多时候,知道每个提交与哪个分支或标记相关联是很有用的。--decorate 标记让 git log 显示指向每次提交的所有引用(例如分支、标签标记等)。

这可以与其他配置选项结合使用。例如,运行 git log --oneline --decorate 会格式化提交历史记录,方式类似于:

0e25143 (HEAD, main) Merge branch 'feature'
ad8621a (feature) Fix a bug in the feature
16b36c6 Add a new feature
23ad9ad (tag: v0.9) Add the initial code base

这让您知道顶层提交也会签出(用 HEAD 表示),它也是 main 分支的尖端。第二次提交有另一个分支指向它,叫做 feature,最后第四次提交被标记为 v0.9

分支、标记、HEAD 和提交历史记录几乎是 Git 存储库中包含的所有信息,因此这可以让您更全面地了解仓库的逻辑结构。

差异

git log 命令包含许多选项,用于显示每次提交的差异。两个最常见的选项是 --stat-p

--stat 选项显示每次提交修改的每个文件的插入和删除次数(请注意,修改一行表示为 1 次插入和 1 次删除)。当您想要摘要来总结每次提交引入的变更时,这会很有用。例如,以下提交在 hello.py 文件中添加了 67 行并删除了 38 行:

commit f2a238924e89ca1d4947662928218a06d39068c3
Author: John <john@example.com>
Date:   Fri Jun 25 17:30:28 2014 -0500

    Add a new feature

 hello.py | 105 ++++++++++++++++++++++++-----------------
 1 file changed, 67 insertion(+), 38 deletions(-)

文件名旁边的 +- 符号表示每个文件被提交修改的相对变更次数。这让您知道在哪里可以找到每次提交变更。

如果您想查看每次提交引入的实际变更,可以将 -p 选项传递给 git log。这会输出代表该提交的整个补丁:

commit 16b36c697eb2d24302f89aa22d9170dfe609855b
Author: Mary <mary@example.com>
Date:   Fri Jun 25 17:31:57 2014 -0500

    Fix a bug in the feature

diff --git a/hello.py b/hello.py
index 18ca709..c673b40 100644
--- a/hello.py
+++ b/hello.py
@@ -13,14 +13,14 @@ B
-print("Hello, World!")
+print("Hello, Git!")

对于有大量变更的提交,生成的输出可能会变得很长而且很笨拙。通常,如果您显示的是完整的补丁,您可能是在搜索特定的变更。为此,您想使用 pickaxe 选项。

Shortlog

git shortlog 命令是 git log 的特殊版本,旨在创建版本公告。它按作者对每个提交分组,并显示每条提交消息的第一行。这是查看谁在做什么的简单方法。

例如,如果两个开发人员为一个项目贡献了 5 次提交,则 git shortlog 的输出可能如下所示:

Mary (2):
      Fix a bug in the feature
      Fix a serious security hole in our framework

John (3):
      Add the initial code base
      Add a new feature
      Merge branch 'feature'

默认情况下,git shortlog 按作者姓名对输出进行排序,但您也可以传递 -n 选项,按每位作者的提交次数进行排序。

--graph 选项绘制一个 ASCII 图,表示提交历史记录的分支结构。它通常与 --oneline--decorate 命令结合使用,以便更容易查看哪个提交属于哪个分支:

git log --graph --oneline --decorate

对于一个只有 2 个分支的简单存储库,这将产生以下结果:

*   0e25143 (HEAD, main) Merge branch 'feature'
|\  
| * 16b36c6 Fix a bug in the new feature
| * 23ad9ad Start a new feature
* | ad8621a Fix a critical security issue
|/  
* 400e4b7 Fix typos in the documentation
* 160e224 Add the initial code base

星号显示提交在哪个分支上,因此上图告诉我们 23ad9ad16b36c6 提交位于主题分支上,其余的都在 main 分支上。

虽然对于简单的存储库来说,这是一个不错的选择,但对于分支较多的项目,最好使用功能更全的可视化工具,比如 gitkSourcetree

自定义格式

对于所有其他 git log 格式化需求,您可以使用 --pretty=format:"" 选项。这使您可以使用 printf 风格的占位符随心所欲地显示每个提交。

例如,以下命令中的 %cn%h%cd 字符分别替换为提交者名称、缩写的提交哈希和提交者日期。

git log --pretty=format:"%cn committed %h on %cd"

这会导致每次提交都采用以下格式:

John committed 400e4b7 on Fri Jun 24 12:30:04 2014 -0500 John committed 89ab2cf on Thu Jun 23 17:09:42 2014 -0500 Mary committed 180e223 on Wed Jun 22 17:21:19 2014 -0500 John committed f12ca28 on Wed Jun 22 13:50:31 2014 -0500

占位符的完整列表可以在完整格式部分找到,该部分在 git log 手册页中。

除了允许您仅查看您感兴趣的信息外,--pretty=format:"" 选项在您尝试将 git log 输出传送到另一个命令中时特别有用。

过滤提交历史记录

格式化每个提交显示方式只是学习 git log 难题的一半。另一半是了解如何浏览提交历史记录。本文的其余部分介绍了一些使用 git log 在项目历史记录中挑选特定提交的高级方法。所有这些都可以与上面讨论的任何格式选项结合使用。

按数量筛选

git log 最基本的筛选选项是限制显示的提交数量。如果您只对最后几次提交感兴趣,这就省去了查看页面中所有提交的麻烦。

您可以通过添加 - 选项来限制 git log 的输出。例如,以下命令将仅显示最近的 3 次提交。

git log -3

按日期筛选

如果您要查找特定时间范围内的提交,则可以使用 --after--before 标记按日期筛选提交。两者都接受各种日期格式作为参数。例如,以下命令仅显示在 2014 年 7 月 1 日(含)之后创建的提交:

git log --after="2014-7-1"

您也可以传递相对引用,比如 "1 week ago""yesterday"

 git log --after="yesterday"

要搜索在两个日期之间创建的提交,可以同时提供 --before--after 日期。例如,要显示在 2014 年 7 月 1 日到 2014 年 7 月 4 日之间添加的所有提交,您可以使用以下内容:

git log --after="2014-7-1" --before="2014-7-4"

请注意,--since--until 标记分别与 --after--before 同义。

按作者筛选

当您只查找特定用户创建的提交时,请使用 --author 标记。它接受正则表达式,并返回作者与该模式匹配的所有提交。如果您确切地知道您在找谁,您可以使用普通的旧字符串代替正则表达式:

git log --author="John"

这将显示作者名字包含 John 的所有提交。作者姓名不必完全匹配,只需要包含指定的短语即可。

您也可以使用正则表达式来创建更复杂的搜索。例如,以下命令搜索由 MaryJohn 提交的内容。

git log --author="John\|Mary"

请注意,作者的电子邮件也包含在作者的姓名中,因此您也可以使用此选项通过电子邮件进行搜索。

如果您的工作流程将提交者和作者分开,则 --committer 标记的运行方式相同。

按消息筛选

要按提交消息筛选提交,请使用 --grep 标记。其工作原理与上面讨论的 --author 标记类似,但它与提交消息匹配,而不是与作者匹配。

例如,如果您的团队在每条提交消息中都包含相关的问题编号,则可以使用以下方法拉取与该问题相关的所有提交:

git log --grep="JRA-224:"

您也可以将 -i 参数传递给 git log,使其在模式匹配时忽略大小写差异。

按文件筛选

很多时候,您只对特定文件发生的变更感兴趣。要显示与文件相关的历史记录,您要做的就是传入文件路径。例如,以下内容返回影响 foo.pybar.py 文件的所有提交:

git log -- foo.py bar.py

-- 参数用于告诉 git log 后续参数是文件路径而不是分支名称。如果不可能将其与分支混在一起,则可以省略 --

按内容筛选

也可以搜索引入或删除特定源代码行的提交。这被称为 pickaxe,采用 -S"" 的形式。例如,如果您想知道 Hello,World! 字符串什么时候添加到项目中的任何文件中,您可以使用以下命令:

git log -S"Hello, World!"

如果要使用正则表达式而不是字符串进行搜索,则可以改用 -G"" 标标记。

这是一个非常强大的调试工具,因为它可以让您找到影响特定代码行的所有提交。它甚至可以显示何时将一行复制或移动到另一个文件中。

按范围筛选

您可以将一系列提交传递给 git log,以仅显示该范围内包含的提交。范围采用以下格式指定,其中 是提交引用:

git log ..

当您使用分支引用作为参数时,此命令特别有用。这是显示两个分支之间差异的简单方法。考虑以下命令:

 git log main..feature

main..feature 范围包含 feature 分支中但不在 main 分支中的所有提交。换句话说,这是自 feature 脱离 main 分支以来所取得的进展。您可以将其可视化为以下内容:

使用范围检测历史记录中的克隆

请注意,如果您切换范围的顺序 (feature..main),您将在 main 中获得所有提交,但不会在 feature 中获得。如果 git log 输出两个版本的提交,则表明您的历史记录存在分歧。

筛选合并提交

默认情况下,git log 在其输出中包含合并提交。但是,如果您的团队有一个始终合并的政策(也就是说,您将上游的变更合并到主题分支中,而不是将主题分支重新建立在上游分支上),那么您的项目历史记录中就会有很多无关的合并提交。

您可以通过传递 --no-merges 标记来阻止 git log 显示这些合并提交:

git log --no-merges

另一方面,如果您对合并提交感兴趣,您可以使用 --merges 标记:

git log --merges

这将返回所有至少有两个父项的提交。

摘要

使用 git log 的高级参数来格式化其输出并选择要显示的提交,您现在应该觉得很便捷,这使您能够从项目历史记录中准确提取所需内容。

这些新技能是 Git 工具包的重要组成部分,但请记住,git log 经常与其他 Git 命令一起使用。找到要查找的提交后,通常将其传递给 git checkoutgit revert 或其他工具来操作提交历史记录。所以,一定要继续学习 Git 的高级功能。

准备好了解 git log 了吗?

试用本交互式教程。

立即开始