查看历史

本节部分内容参考自 Pro Gitgit-recipes,在原文基础上有一定修改。

git log

在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。完成这个任务最简单而又有效的工具是 git log 命令。

下面是一个典型的 git log 输出:

$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    removed unnecessary test

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    first commit

不传入任何参数的默认情况下,git log 会按时间先后顺序列出所有的提交,最近的更新排在最上面。正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。

有的时候,在 Windows 上使用 git log 时,会在输出一段信息后卡住。这是因为 git log 在输出过长时,会调用分页器来实现分屏输出,但 Git for Windows 自带的分页器有一定的不兼容。这种情况下,可以使用 git config --global core.pager cat 来禁用分页功能。

筛选历史

git log -n <limit>

显示前 <limit> 个提交。比如 git log -n 3 只会显示 3 个提交。-n 3 也可以写成 -3

git log --author="<pattern>"

搜索特定作者的提交。<pattern> 可以是字符串或正则表达式。

git log --grep="<pattern>"

搜索特定提交信息的提交。<pattern> 可以是字符串或正则表达式。

git log <since>..<until>

只显示发生在 <since><until> 之间的提交。两个参数可以是提交 ID、分支名、HEAD 或是任何一种引用。

git log <file>

只显示包含特定文件的提交。查找特定文件的历史这样做会很方便。

git log --since=<since>
git log --until=<until>

仅显示指定时间之后或之前的提交。<since><until> 可以为多种时间格式,可以是类似 "2008-01-15" 的具体的某一天,也可以是类似 "2 years 1 day 3 minutes ago" 的相对日期。

git log -S <name>

仅显示添加或删除特定字符串的提交。比如,搜索代码中添加或删除了对某一个特定函数的引用的提交。

显示更多信息

-p--patch 选项会显示每次提交所引入的差异(按补丁的格式输出)。

$ git log -p -1
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

diff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
 spec = Gem::Specification.new do |s|
     s.platform  =   Gem::Platform::RUBY
     s.name      =   "simplegit"
-    s.version   =   "0.1.0"
+    s.version   =   "0.1.1"
     s.author    =   "Scott Chacon"
     s.email     =   "schacon@gee-mail.com"
     s.summary   =   "A simple gem for using Git in Ruby code."

--stat 选项可以显示每次提交的简略统计信息,包括所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加。

$ git log --stat -1
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    first commit

 README           |  6 ++++++
 Rakefile         | 23 +++++++++++++++++++++++
 lib/simplegit.rb | 25 +++++++++++++++++++++++++
 3 files changed, 54 insertions(+)

格式化输出

--format 选项可以定制记录的显示格式。这样的输出对后期提取分析格外有用——因为你知道输出的格式不会随着 Git 的更新而发生改变:

$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : changed the version number
085bb3b - Scott Chacon, 6 years ago : removed unnecessary test
a11bef0 - Scott Chacon, 6 years ago : first commit

下面列出了 --format 接受的常用格式占位符的写法及其代表的意义。

占位符说明
%H提交的完整哈希值
%h提交的短哈希值
%T树的完整哈希值
%t树的短哈希值
%P父提交的完整哈希值
%p父提交的简写哈希值
%an作者名字
%ae作者的电子邮件地址
%ad作者修订日期(可以用 --date=选项 来定制格式)
%ar作者修订日期,按多久以前的方式显示
%cn提交者的名字
%ce提交者的电子邮件地址
%cd提交日期
%cr提交日期(距今多长时间)
%s提交说明

你一定奇怪作者和提交者之间究竟有何差别,其实作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。所以,当你为某个项目发布补丁,然后某个核心成员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者。关于其中具体的区别,我们会在以后的章节中详细讨论。

format 与另一个 log 选项 --graph 结合使用时尤其有用。 这个选项添加了一些 ASCII 字符串来形象地展示你的分支、合并历史:

$ git log --pretty=format:"%h %s" --graph
* 2d3acf9 ignore errors from SIGCHLD on trap
*  5e3ee11 Merge branch 'master' of git://github.com/dustin/grit
|\
| * 420eac9 Added a method for getting the current branch.
* | 30e367c timeout code and tests
* | 5a09431 add timeout protection to grit
* | e1193f8 support for heads with slashes in them
|/
* d6016bc require time for xmlschema
*  11d191e Merge branch 'defunkt' into local

下面是我很喜欢的一种 log 格式,在纵览项目历史时很有用:

git log --no-merges --color --graph --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit

可以为这一长串命令起一个别名:

git config --global alias.ls "log --no-merges --color --graph --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit"

之后,就可以用 git ls 来快速调用这个命令。

$ git ls -10
* e7a595e - (HEAD -> 查看历史) :adhesive_bandage: 更正关于 Linux 上的文本编辑器的描述 (2022-06-17 23:19:29) <忘忧北萱草>
* 0089bc1 - :sparkles: 更新关于 GPG 出错的解决方案 (2022-06-17 23:16:33) <忘忧北萱草>
* 23b7ae1 - :sparkles: 提交更改 (2022-06-17 18:20:09) <忘忧北萱草>
* 4fb486c - :sparkles: 本项目的 gitmoji 规范 (2022-06-17 16:08:40) <忘忧北萱草>
* 35ecf52 - :adhesive_bandage: 修正关于储存库中文件储存方式的描述 (2022-06-17 15:57:25) <忘忧北萱草>
* b571156 - :zap: 添加 giscus 支持,以及修复导航按钮问题 (2022-06-17 01:59:31) <忘忧北萱草>
* 4e84d9d - :sparkles: 管理暂存区 (2022-06-16 23:05:09) <忘忧北萱草>
* ad3b4fc - :pencil2: 规范标点的使用 (2022-06-16 19:13:06) <忘忧北萱草>
* 64273e0 - :memo: 增加 badges 和链接 (2022-06-16 17:28:53) <忘忧北萱草>
* 30cae30 - :lipstick: 优化点击跳转的平滑滚动的方式和效果 (2022-06-16 16:38:37) <忘忧北萱草>

在钩子中使用 git log

后来我发现在 pre-push 钩子里使用交互式命令会破坏一些自动化工具的使用,所以是否使用下面的钩子,还是要看你自己的情况。

上一节我们说过,必须重视每一次提交,保证不完整的提交记录不会被推送到远程。

但是,Git 并非一个所见即所得的系统,有时候你做出了一些更改,并不能立刻想到结果是到底是什么样的。这时 git log 会是很好的帮助,它可以帮你检查你做出的更改是否有问题。只要更改还留在本地,你永远有修正的机会。

不过,假如在提推送前你忘记了用 git log 查看一下,就有可能发生一些不好的事情。所以,我们需要一个东西,来提醒我们在每次推送之前 git log 一下。这种东西就是 Git 钩子

关于钩子的详细说明,将会在以后的章节涉及。这里就简单举一个使用钩子的实例。

储存库所使用的钩子存放在 .git/hooks 目录中。将其中 pre-push.sample 文件改名为 pre-push,去掉 .sample 来启用它。这个钩子会在每次推送前被调用。

pre-push 的内容修改为:

#!/bin/bash

remote="$1"
url="$2"

echo "You are pushing to $remote($url), please check the commits history: "

git log --no-merges --color --graph --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit -10

echo ""
read -r -p "Type 'y' to continue or 'n' to abort: " confirm < /dev/tty
if [[ $confirm =~ ^[Yy]$ ]]; then
    exit 0
else
    echo "Abort."
    exit 1
fi

这样,每次推送前,都会自动显示最近10条提交记录,并询问是否继续,这就给了我们反悔的机会。

$ git push
You are pushing to origin(git@github.com:Wybxc/git-remake-guide.git), please check the commits history:
* e7a595e - (HEAD -> master) :adhesive_bandage: 更正关于 Linux 上的文本编辑器的描述 (2022-06-17 23:19:29) <忘忧北萱草>
* 0089bc1 - :sparkles: 更新关于 GPG 出错的解决方案 (2022-06-17 23:16:33) <忘忧北萱草>
* 23b7ae1 - :sparkles: 提交更改 (2022-06-17 18:20:09) <忘忧北萱草>
* 4fb486c - :sparkles: 本项目的 gitmoji 规范 (2022-06-17 16:08:40) <忘忧北萱草>
* 35ecf52 - :adhesive_bandage: 修正关于储存库中文件储存方式的描述 (2022-06-17 15:57:25) <忘忧北萱草>
* b571156 - :zap: 添加 giscus 支持,以及修复导航按钮问题 (2022-06-17 01:59:31) <忘忧北萱草>
* 4e84d9d - :sparkles: 管理暂存区 (2022-06-16 23:05:09) <忘忧北萱草>
* ad3b4fc - :pencil2: 规范标点的使用 (2022-06-16 19:13:06) <忘忧北萱草>
* 64273e0 - :memo: 增加 badges 和链接 (2022-06-16 17:28:53) <忘忧北萱草>
* 30cae30 - :lipstick: 优化点击跳转的平滑滚动的方式和效果 (2022-06-16 16:38:37) <忘忧北萱草>
Type 'y' to continue or 'n' to abort: y
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 882 bytes | 882.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To git@github.com:Wybxc/git-remake-guide.git
 * [new branch]      master -> master
branch 'master' set up to track 'origin/master'.

如果你觉得这个钩子很好,并且想要给其他的仓库也启用,你可以修改 Git 创建新储存库所用的模板仓库,它的目录在 /usr/share/git-core/templates 或者 <Git for Windows 目录>/mingw64/share/git-core/templates。这会影响之后新建或者新克隆的储存库,而之前已经建立的储存库是不受影响的。