逸才 · 更新于 2018-11-28 11:00:43

检查与比较

现在你有了一堆分支,短期的主题、长期的特性或者其它。怎样追踪他们呢? Git 有一组工具,可以帮助你弄明白工作是在哪儿完成的,两个分支间的区别是啥,等等。

简而言之 执行 git log 找到你的项目历史中的特定提交 —— 按作者、日期、内容或者历史记录。执行 git diff 比较历史记录中的两个不同的点 —— 通常是为了看看两个分支有啥区别,或者从某个版本到另一个版本,你的软件都有啥变化。

git log 过滤你的提交历史记录

通过查看分支中另一分支看不到的提交记录,我们已经看到如何用 git log 来比较分支。 (如果你不记得了,它看起来是这样的:git log branchA ^branchB)。 而且,你也可以用 git log 去寻找特定的提交。 在此,我们会看到一些更广为使用的 git log 选项,不过哪有很多。 完整的清单可以看看官方文档。

git log --author 只寻找某个特定作者的提交

要过滤你的提交历史,只寻找某个特定作者的提交,你可以使用 --author 选项。 例如,比方说我们要找 Git 源码中 Linus 提交的部分。 我们可以执行类似 git log --author=Linus 的命令。 这个查找是大小写敏感的,并且也会检索电子邮箱地址。 我在此例中使用 -[number] 选项,以限制结果为最近 [number] 次的提交。

$ git log --author=Linus --oneline -5
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
3bb7256 make "index-pack" a built-in
377d027 make "git pack-redundant" a built-in
b532581 make "git unpack-file" a built-in
112dd51 make "mktag" a built-in

git log --since --before 根据日期过滤提交记录

如果你要指定一个你感兴趣的日期范围以过滤你的提交,可以执行几个选项 —— 我用 --since--before,但是你也可以用 --until--after。 例如,如果我要看 Git 项目中三周前且在四月十八日之后的所有提交,我可以执行这个(我还用了 --no-merges 选项以隐藏合并提交):

$ git log --oneline --before={3.weeks.ago} --after={2010-04-18} --no-merges
5469e2d Git 1.7.1-rc2
d43427d Documentation/remote-helpers: Fix typos and improve language
272a36b Fixup: Second argument may be any arbitrary string
b6c8d2d Documentation/remote-helpers: Add invocation section
5ce4f4e Documentation/urls: Rewrite to accomodate transport::address
00b84e9 Documentation/remote-helpers: Rewrite description
03aa87e Documentation: Describe other situations where -z affects git diff
77bc694 rebase-interactive: silence warning when no commits rewritten
636db2c t3301: add tests to use --format="%N"

git log --grep 根据提交注释过滤提交记录

你或许还想根据提交注释中的某个特定短语查找提交记录。可以用 --grep 选项。 比如说我知道有个提交是有关使用 P4EDITOR 环境变量,又想回忆起那个改动是啥样子的 —— 我可以用 --grep 选项找到该提交。

$ git log --grep=P4EDITOR --no-merges
commit 82cea9ffb1c4677155e3e2996d76542502611370
Author: Shawn Bohrer
Date:   Wed Mar 12 19:03:24 2008 -0500

    git-p4: Use P4EDITOR environment variable when set

    Perforce allows you to set the P4EDITOR environment variable to your
    preferred editor for use in perforce.  Since we are displaying a
    perforce changelog to the user we should use it when it is defined.

    Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>
    Signed-off-by: Simon Hausmann <simon@lst.de>

Git 会对所有的 --grep--author 参数作逻辑或。 如果你用 --grep--author 时,想看的是某人写作的并且有某个特殊的注释内容的提交记录, 你需要加上 --all-match 选项。 在这些例子中,我会用上 --format 选项,这样我们就可以看到每个提交的作者是谁了。

如果我查找注释内容含有 “p4 depo”的提交,我得到了三个提交:

$ git log --grep="p4 depo" --format="%h %an %s"
ee4fd1a Junio C Hamano Merge branch 'master' of git://repo.or.cz/git/fastimport
da4a660 Benjamin Sergeant git-p4 fails when cloning a p4 depo.
1cd5738 Simon Hausmann Make incremental imports easier to use by storing the p4 d

如果我加上 --author=Hausmann 参数,与进一步过滤上述结果到 Simon 的唯一提交相反, 它会告诉我所有 Simon 的提交,或者注释中有“p4 demo”的提交:

$ git log --grep="p4 depo" --format="%h %an %s" --author="Hausmann"
cdc7e38 Simon Hausmann Make it possible to abort the submission of a change to Pe
f5f7e4a Simon Hausmann Clean up the git-p4 documentation
30b5940 Simon Hausmann git-p4: Fix import of changesets with file deletions
4c750c0 Simon Hausmann git-p4: git-p4 submit cleanups.
0e36f2d Simon Hausmann git-p4: Removed git-p4 submit --direct.
edae1e2 Simon Hausmann git-p4: Clean up git-p4 submit's log message handling.
4b61b5c Simon Hausmann git-p4: Remove --log-substitutions feature.
36ee4ee Simon Hausmann git-p4: Ensure the working directory and the index are cle
e96e400 Simon Hausmann git-p4: Fix submit user-interface.
38f9f5e Simon Hausmann git-p4: Fix direct import from perforce after fetching cha
2094714 Simon Hausmann git-p4: When skipping a patch as part of "git-p4 submit" m
1ca3d71 Simon Hausmann git-p4: Added support for automatically importing newly ap
...

不过,如果加上 --all-match,结果就是我想要的了:

$ git log --grep="p4 depo" --format="%h %an %s" --author="Hausmann" --all-match
1cd5738 Simon Hausmann Make incremental imports easier to use by storing the p4 d

git log -S 依据所引入的差值过滤

如果你写的提交注释都极度糟糕怎么办?或者,如果你要找某个函数是何时引入的,某些变量是在哪里开始被使用的? 你可以告诉 Git 在每个提交之间的差值中查找特定字符串。 例如,如果我们想要找出哪个提交修改出了类似函数名“userformat_find_requirements”, 我们可以执行(注意在“-S”与你要找的东东之间没有“=”):

$ git log -Suserformat_find_requirements
commit 5b16360330822527eac1fa84131d185ff784c9fb
Author: Johannes Gilger
Date:   Tue Apr 13 22:31:12 2010 +0200

    pretty: Initialize notes if %N is used

    When using git log --pretty='%N' without an explicit --show-notes, git
    would segfault. This patches fixes this behaviour by loading the needed
    notes datastructures if --pretty is used and the format contains %N.
    When --pretty='%N' is used together with --no-notes, %N won't be
    expanded.

    This is an extension to a proposed patch by Jeff King.

    Signed-off-by: Johannes Gilger
    Signed-off-by: Junio C Hamano

git log -p 显示每个提交引入的补丁

每个提交都是项目的一个快照。由于每个提交都记录它所基于的快照,Git 能够经常对它们求差值,并以补丁形式向你展示。 这意味着,对任意提交,你都可以获取该提交给项目引入补丁。 你可以用 git show [SHA] 加上某个特定的提交 SHA 获取,或者执行 git log -p, 它会告诉 Git 输出每个提交之后的补丁。这是个总结某一分支或者两个提交之间都发生了神马的好途径。

$ git log -p --no-merges -2
commit 594f90bdee4faf063ad07a4a6f503fdead3ef606
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jun 4 15:46:55 2010 +0200

    reverted to old class name

diff --git a/ruby.rb b/ruby.rb
index bb86f00..192151c 100644
--- a/ruby.rb
+++ b/ruby.rb
@@ -1,7 +1,7 @@
-class HiWorld
+class HelloWorld
   def self.hello
     puts "Hello World from Ruby"
   end
 end

-HiWorld.hello
+HelloWorld.hello

commit 3cbb6aae5c0cbd711c098e113ae436801371c95e
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jun 4 12:58:53 2010 +0200

    fixed readme title differently

diff --git a/README b/README
index d053cc8..9103e27 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Hello World Examples
+Many Hello World Examples
 ======================

 This project has examples of hello world in

这是个总结改动,以及合并或发布之前重审一系列提交的好方式。

git log --stat 显示每个提交引入的改动的差值统计

如果 -p 选项对你来说太详细了,你可以用 --stat 总结这些改动。 这是不用 -p,而用 --stat 选项时,同一份日志的输出。

$ git log --stat --no-merges -2
commit 594f90bdee4faf063ad07a4a6f503fdead3ef606
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jun 4 15:46:55 2010 +0200

    reverted to old class name

 ruby.rb |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

commit 3cbb6aae5c0cbd711c098e113ae436801371c95e
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jun 4 12:58:53 2010 +0200

    fixed readme title differently

 README |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

同样的基本信息,但更紧凑 —— 它仍然让你看到相对改动,和改动了哪些文件。

git diff

最后,要查看两个提交快照的绝对改动,你可以用 git diff 命令。 这在两个主要情况中广为使用 —— 查看两个分支彼此之间的差值,和查看自发布或者某个旧历史点之后都有啥变了。让我们看看这俩情况。

你仅需执行 git diff [version](或者你给该发布打的任何标签)就可以查看自最近发布之后的改动。 例如,如果我们想要看看自 v0.9 发布之后我们的项目改变了啥,我们可以执行 git diff v0.9

$ git diff v0.9
diff --git a/README b/README
index d053cc8..d4173d5 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Hello World Examples
+Many Hello World Lang Examples
 ======================

 This project has examples of hello world in
diff --git a/ruby.rb b/ruby.rb
index bb86f00..192151c 100644
--- a/ruby.rb
+++ b/ruby.rb
@@ -1,7 +1,7 @@
-class HiWorld
+class HelloWorld
   def self.hello
     puts "Hello World from Ruby"
   end
 end

-HiWorld.hello
+HelloWorld.hello

正如 git log,你可以给它加上 --stat 参数。

$ git diff v0.9 --stat
 README  |    2 +-
 ruby.rb |    4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

要比较两个不同的分支,你可以执行类似 git diff branchA branchB 的命令。 不过它的问题在于它会完完全全按你说的作 —— 它会直接给你个补丁文件,该补丁能够将甲分支的最新快照变成乙分支的最新快照的样子。 这意味着如果两个分支已经产生分歧 —— 奔往两个不同方向了 —— 它会移除甲分支中引入的所有工作,然后累加乙分支中的所有工作。 这大概不是你要的吧 —— 你想要不在甲分支中的乙分支的改动。所以你真的需要的是两个分支叉开去时,和最新的乙分支的差别。 所以,如果我们的历史记录看起来像这样:

$ git log --graph --oneline --decorate --all
* 594f90b (HEAD, tag: v1.0, master) reverted to old class name
| * 1834130 (erlang) added haskell
| * ab5ab4c added erlang
|/
*   8d585ea Merge branch 'fix_readme'
...

并且,我们想要看“erlang”分支与主分支相比的查别。执行 git diff master erlang 会给我们错误的结果。

$ git diff --stat master erlang
 erlang_hw.erl |    5 +++++
 haskell.hs    |    4 ++++
 ruby.rb       |    4 ++--
 3 files changed, 11 insertions(+), 2 deletions(-)

你可以看到,它加上了 erlang 和 haskell 文件,这确实是我们在该分支中做的, 但是它同时恢复了我们在主分支中改动的 ruby 文件。我们真心想要的只是“erlang”分支中的改动(添加两个文件)。 我们可以通过求两个分支分歧时的共同提交与该分支的差值得到想要的结果:

$ git diff --stat 8d585ea erlang
 erlang_hw.erl |    5 +++++
 haskell.hs    |    4 ++++
 2 files changed, 9 insertions(+), 0 deletions(-)

这才是我们在找的,但是我们可不想要每次都要找出两个分支分歧时的那次提交。 幸运的是,Git 为此提供了一个快捷方式。 如果你执行 git diff master...erlang(在分支名之间有三个半角的点), Git 就会自动找出两个分支的共同提交(也被成为合并基础),并求差值。

$ git diff --stat master erlang
 erlang_hw.erl |    5 +++++
 haskell.hs    |    4 ++++
 ruby.rb       |    4 ++--
 3 files changed, 11 insertions(+), 2 deletions(-)
$ git diff --stat master...erlang
 erlang_hw.erl |    5 +++++
 haskell.hs    |    4 ++++
 2 files changed, 9 insertions(+), 0 deletions(-)

几乎每一次你要对比两个分支的时候,你都会想用三个点的语法,因为它通常会给你你想要的。

顺带提一句,你还可以让 Git 手工计算两次提交的合并基础(第一个共同的祖提交),即 git merge-base 命令:

$ git merge-base master erlang
8d585ea6faf99facd39b55d6f6a3b3f481ad0d3d

所以你执行下面这个也跟 git diff master...erlang 一样:

$ git diff --stat $(git merge-base master erlang) erlang
 erlang_hw.erl |    5 +++++
 haskell.hs    |    4 ++++
 2 files changed, 9 insertions(+), 0 deletions(-)

当然,我会推荐简单点的那个。

简而言之 使用 git diff 查看某一分支自它偏离出来起与过去某一点之间项目的改动。 总是使用 git diff branchA...branchB 来查看 branchB 与 branchA 的相对差值,这会让事情简单点。

就是这样了! 请阅读 《Pro Git》 以获得更多信息。