[[r_viewing_history]]
=== 查看提交历史

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

我们使用一个非常简单的 ``simplegit'' 项目作为示例。
运行下面的命令获取该项目：

[source,console]
----
$ git clone https://github.com/schacon/simplegit-progit
----

当你在此项目中运行 `git log` 命令时，可以看到下面的输出：(((git commands, log)))

[source,console]
----
$ 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 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。

`git log` 有许多选项可以帮助你搜寻你所要找的提交，
下面我们会介绍几个最常用的选项。

其中一个比较有用的选项是 `-p`，它会显示每次提交所引入的差异。
与此同时，你也可以使用 `-2` 选项来仅显示最近的两次提交：

[source,console]
----
$ git log -p -2
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."

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

    removed unnecessary test

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index a0a60ae..47c6340 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -18,8 +18,3 @@ class SimpleGit
     end

 end
-
-if $0 == __FILE__
-  git = SimpleGit.new
-  puts git.show
-end
\ No newline at end of file
----

该选项除了显示基本信息之外，还附带了每次 commit 的变化。
当进行代码审查，或者快速浏览某个搭档提交的 commit 所带来的变化的时候，这个参数就非常有用了。
你也可以为 `git log` 附带一系列的总结性选项。
比如你想看到每次提交的简略统计信息，可以使用 `--stat` 选项：

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

    changed the version number

 Rakefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

    removed unnecessary test

 lib/simplegit.rb | 5 -----
 1 file changed, 5 deletions(-)

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(+)
----

正如你所看到的，`--stat` 选项在每次提交的下面列出所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。
在每次提交的最后还有一个总结。

另一个非常有用的选项是 `--pretty`。
这个选项可以使用不同于默认格式的方式展示提交历史。
这个选项有一些内建的子选项供你使用。
比如 `oneline` 会将每个提交放在一行显示，在浏览大量的提交时非常有用。
另外还有 `short`，`full` 和 `fuller` 选项，它们展示信息的格式基本一致，但是详尽程度不一：

[source,console]
----
$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit
----

但最有意思的是 format，可以定制要显示的记录格式。
这样的输出对后期提取分析格外有用 -- 因为你知道输出的格式不会随着 Git 的更新而发生改变：(((log formatting)))

[source,console]
----
$ 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
----

<<rpretty_format>> 列出了常用的格式占位符写法及其代表的意义。

[[rpretty_format]]
.`git log --pretty=format` 常用的选项
[cols="1,4",options="header"]
|================================
| 选项   | 说明
| `%H`     | 提交的完整哈希值
| `%h`     | 提交的简写哈希值
| `%T`     | 树的完整哈希值
| `%t`     | 树的简写哈希值
| `%P`     | 父提交的完整哈希值
| `%p`     | 父提交的简写哈希值
| `%an`    | 作者名字
| `%ae`    | 作者的电子邮件地址
| `%ad`    | 作者修订日期（可以用 --date= 选项定制格式）
| `%ar`    | 作者修订日期，按多久以前的方式显示
| `%cn`    | 提交者（committer）的名字
| `%ce`    | 提交者的电子邮件地址
| `%cd`    | 提交日期
| `%cr`    | 提交日期（距今多长时间）
| `%s`     | 提交说明
|================================

你一定奇怪 _作者_ 和 _提交者_ 之间究竟有何差别，
其实作者指的是实际作出修改的人，提交者指的是最后将此工作成果提交到仓库的人。
所以，当你为某个项目发布补丁，然后某个核心成员将你的补丁并入项目时，你就是作者，而那个核心成员就是提交者。
我们会在 <<ch05-distributed-git#ch05-distributed-git>> 再详细介绍两者之间的细微差别。

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

[source,console]
----
$ 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
----

这种输出类型会在我们下一章学完分支与合并以后变得更加有趣。

以上只是简单介绍了一些 `git log` 命令支持的选项。
<<rlog_options>> 列出了我们目前涉及到的和没涉及到的选项，以及它们是如何影响 log 命令的输出的：

[[rlog_options]]
.`git log` 的常用选项
[cols="1,4",options="header"]
|================================
| 选项            | 说明
| `-p`              | 按补丁格式显示每个提交引入的差异。
| `--stat`          | 显示每次提交的文件修改统计信息。
| `--shortstat`     | 只显示 --stat 中最后的行数修改添加移除统计。
| `--name-only`     | 仅在提交信息后显示已修改的文件清单。
| `--name-status`   | 显示新增、修改、删除的文件清单。
| `--abbrev-commit` | 仅显示 SHA-1 校验和所有 40 个字符中的前几个字符。
| `--relative-date` | 使用较短的相对时间而不是完整格式显示日期（比如，``2 weeks ago''）。
| `--graph`         | 在日志旁以 ASCII 图形显示分支与合并历史。
| `--pretty`        | 使用其他格式显示历史提交信息。可用的选项包括 oneline，short，full，fuller 和 format（用来定义自己的格式）。
|================================

==== 限制输出长度

除了定制输出格式的选项之外，`git log` 还有许多非常实用的限制输出长度的选项，也就是只输出一部分的提交。
之前你已经看到过 `-2` 选项了，它只会显示最近的两条提交，
实际上，你可以使用类似 `-<n>` 的选项，其中的 `n` 可以是任何整数，表示仅显示最近的 `n` 条提交。
不过实践中这个选项不是很常用，因为 Git 默认会将所有的输出传送到分页程序中，所以你一次只会看到一页的内容。

但是，类似 `--since` 和 `--until` 这种按照时间作限制的选项很有用。
例如，下面的命令会列出最近两周的所有提交：

[source,console]
----
$ git log --since=2.weeks
----

该命令可用的格式十分丰富——可以是类似 `"2008-01-15"` 的具体的某一天，也可以是类似 `"2 years 1 day 3 minutes ago"` 的相对日期。

还可以过滤出匹配指定条件的提交。
用 `--author` 选项显示指定作者的提交，用 `--grep` 选项搜索提交说明中的关键字。
（请注意，如果你要同时对作者和提交说明进行过滤，就必须添加 `--all-match` 选项，否则该命令将会匹配满足其中任意一个条件的提交）

另一个非常有用的过滤器是 `-S`，它接受一个字符串参数，并且只会显示那些添加或删除了该字符串的提交。
假设你想找出添加或删除了对某一个特定函数的引用的提交，可以调用：

[source,console]
----
$ git log -Sfunction_name
----

最后一个很实用的 `git log` 选项是路径（path），
如果只关心某些文件或者目录的历史提交，可以在 git log 选项的最后指定它们的路径。
因为是放在最后位置上的选项，所以用两个短划线（--）隔开之前的选项和后面限定的路径名。

在 <<rlimit_options>> 中列出了常用的选项

[[rlimit_options]]
.限制 `git log` 输出的选项
[cols="2,4",options="header"]
|================================
| 选项                | 说明
| `-(n)`                | 仅显示最近的 n 条提交。
| `--since`, `--after`  | 仅显示指定时间之后的提交。
| `--until`, `--before` | 仅显示指定时间之前的提交。
| `--author`            | 仅显示作者匹配指定字符串的提交。
| `--committer`         | 仅显示提交者匹配指定字符串的提交。
| `--grep`              | 仅显示提交说明中包含指定字符串的提交。
| `-S`                  | 仅显示添加或删除内容匹配指定字符串的提交。
|================================

来看一个实际的例子，如果要查看 2008 年 10 月期间 Git 源代码仓库中，由 Junio Hamano 提交的修改了测试文件并且尚未合并的提交，可以使用下面的命令:(((log filtering)))

[source,console]
----
$ git log --pretty="%h - %s" --author=gitster --since="2008-10-01" \
   --before="2008-11-01" --no-merges -- t/
5610e3b - Fix testcase failure when extended attributes are in use
acd3b9e - Enhance hold_lock_file_for_{update,append}() API
f563754 - demonstrate breakage of detached checkout with symbolic link HEAD
d1a43f2 - reset --hard/read-tree --reset -u: remove unmerged new paths
51a94af - Fix "checkout --track -b newbranch" on detached HEAD
b0ad11e - pull: allow "git pull origin $something:$current_branch" into an unborn branch
----

在近 40000 条提交中，上面的输出仅列出了符合条件的 6 条记录。
