Git 内部原理 - (7)维护与数据恢复 (8) 环境变量 (9)总结

维护与数据恢复

有的时候,你需要对仓库进行清理 - 使它的结构变得更紧凑,或是对导入的仓库进行清理,或是恢复丢失的内容。 这个小节将会介绍这些情况中的一部分。

维护

Git 会不定时地自动运行一个叫做 “auto gc” 的命令。 大多数时候,这个命令并不会产生效果。 然而,如果有太多松散对象(不在包文件中的对象)或者太多包文件,Git 会运行一个完整的 git gc 命令。 “gc” 代表垃圾回收,这个命令会做以下事情:收集所有松散对象并将它们放置到包文件中,将多个包文件合并为一个大的包文件,移除与任何提交都不相关的陈旧对象。

可以像下面一样手动执行自动垃圾回收:

$ git gc --auto

就像上面提到的,这个命令通常并不会产生效果。 大约需要 7000 个以上的松散对象或超过 50 个的包文件才能让 Git 启动一次真正的 gc 命令。 你可以通过修改 gc.auto 与 gc.autopacklimit 的设置来改动这些数值。

gc 将会做的另一件事是打包你的引用到一个单独的文件。 假设你的仓库包含以下分支与标签:

$ find .git/refs -type f
.git/refs/heads/experiment
.git/refs/heads/master
.git/refs/tags/v1.0
.git/refs/tags/v1.1

如果你执行了 git gc 命令,refs 目录中将不会再有这些文件。 为了保证效率 Git 会将它们移动到名为 .git/packed-refs 的文件中,就像这样:

$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9

如果你更新了引用,Git 并不会修改这个文件,而是向 refs/heads 创建一个新的文件。 为了获得指定引用的正确 SHA-1 值,Git 会首先在 refs 目录中查找指定的引用,然后再到 packed-refs 文件中查找。 所以,如果你在 refs 目录中找不到一个引用,那么它或许在 packed-refs 文件中。

注意这个文件的最后一行,它会以 ^ 开头。 这个符号表示它上一行的标签是附注标签,^ 所在的那一行是附注标签指向的那个提交。

数据恢复

在你使用 Git 的时候,你可能会意外丢失一次提交。 通常这是因为你强制删除了正在工作的分支,但是最后却发现你还需要这个分支;亦或者硬重置了一个分支,放弃了你想要的提交。 如果这些事情已经发生,该如何找回你的提交呢?

下面的例子将硬重置你的测试仓库中的 master 分支到一个旧的提交,以此来恢复丢失的提交。 首先,让我们看看你的仓库现在在什么地方:

$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

现在,我们将 master 分支硬重置到第三次提交:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

现在顶部的两个提交已经丢失了 - 没有分支指向这些提交。 你需要找出最后一次提交的 SHA-1 然后增加一个指向它的分支。 窍门就是找到最后一次的提交的 SHA-1 - 但是估计你记不起来了,对吗?

最方便,也是最常用的方法,是使用一个名叫 git reflog 的工具。 当你正在工作时,Git 会默默地记录每一次你改变 HEAD 时它的值。 每一次你提交或改变分支,引用日志都会被更新。 引用日志(reflog)也可以通过 git update-ref 命令更新,我们在 Git 引用 有提到使用这个命令而不是是直接将 SHA-1 的值写入引用文件中的原因。 你可以在任何时候通过执行 git reflog 命令来了解你曾经做过什么:

$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb

这里可以看到我们已经检出的两次提交,然而并没有足够多的信息。 为了使显示的信息更加有用,我们可以执行 git log -g,这个命令会以标准日志的格式输出引用日志。

$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:22:37 2009 -0700 third commit commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700 modified repo.rb a bit

看起来下面的那个就是你丢失的提交,你可以通过创建一个新的分支指向这个提交来恢复它。 例如,你可以创建一个名为 recover-branch 的分支指向这个提交(ab1afef):

$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

不错,现在有一个名为 recover-branch 的分支是你的 master 分支曾经指向的地方,再一次使得前两次提交可到达了。 接下来,假设你丢失的提交因为某些原因不在引用日志中 - 我们可以通过移除 recover-branch 分支并删除引用日志来模拟这种情况。 现在前两次提交又不被任何分支指向了:

$ git branch -D recover-branch
$ rm -Rf .git/logs/

由于引用日志数据存放在 .git/logs/ 目录中,现在你已经没有引用日志了。 这时该如何恢复那次提交? 一种方式是使用 git fsck 实用工具,将会检查数据库的完整性。 如果使用一个 --full 选项运行它,它会向你显示出所有没有被其他对象指向的对象:

$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18/18), done.
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

在这个例子中,你可以在 “dangling commit” 后看到你丢失的提交。 现在你可以用和之前相同的方法恢复这个提交,也就是添加一个指向这个提交的分支。

移除对象

Git 有很多很棒的功能,但是其中一个特性会导致问题,git clone 会下载整个项目的历史,包括每一个文件的每一个版本。 如果所有的东西都是源代码那么这很好,因为 Git 被高度优化来有效地存储这种数据。 然而,如果某个人在之前向项目添加了一个大小特别大的文件,即使你将这个文件从项目中移除了,每次克隆还是都要强制的下载这个大文件。 之所以会产生这个问题,是因为这个文件在历史中是存在的,它会永远在那里。

当你迁移 Subversion 或 Perforce 仓库到 Git 的时候,这会是一个严重的问题。 因为这些版本控制系统并不下载所有的历史文件,所以这种文件所带来的问题比较少。 如果你从其他的版本控制系统迁移到 Git 时发现仓库比预期的大得多,那么你就需要找到并移除这些大文件。

警告:这个操作对提交历史的修改是破坏性的。 它会从你必须修改或移除一个大文件引用最早的树对象开始重写每一次提交。 如果你在导入仓库后,在任何人开始基于这些提交工作前执行这个操作,那么将不会有任何问题 - 否则,你必须通知所有的贡献者他们需要将他们的成果变基到你的新提交上。

为了演示,我们将添加一个大文件到测试仓库中,并在下一次提交中删除它,现在我们需要找到它,并将它从仓库中永久删除。 首先,添加一个大文件到仓库中:

$ curl https://www.kernel.org/pub/software/scm/git/git-2.1.0.tar.gz > git.tgz
$ git add git.tgz
$ git commit -m 'add git tarball'
[master 7b30847] add git tarball
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 git.tgz

哎呀 - 其实这个项目并不需要这个巨大的压缩文件。 现在我们将它移除:

$ git rm git.tgz
rm 'git.tgz'
$ git commit -m 'oops - removed large tarball'
[master dadf725] oops - removed large tarball
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 git.tgz

现在,我们执行 gc 来查看数据库占用了多少空间:

$ git gc
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)

你也可以执行 count-objects 命令来快速的查看占用空间大小:

$ git count-objects -v
count: 7
size: 32
in-pack: 17
packs: 1
size-pack: 4868
prune-packable: 0
garbage: 0
size-garbage: 0

size-pack 的数值指的是你的包文件以 KB 为单位计算的大小,所以你大约占用了 5MB 的空间。 在最后一次提交前,使用了不到 2KB - 显然,从之前的提交中移除文件并不能从历史中移除它。 每一次有人克隆这个仓库时,他们将必须克隆所有的 5MB 来获得这个微型项目,只因为你意外地添加了一个大文件。 现在来让我们彻底的移除这个文件。

首先你必须找到它。 在本例中,你已经知道是哪个文件了。 但是假设你不知道;该如何找出哪个文件或哪些文件占用了如此多的空间? 如果你执行 git gc 命令,所有的对象将被放入一个包文件中,你可以通过运行 git verify-pack 命令,然后对输出内容的第三列(即文件大小)进行排序,从而找出这个大文件。 你也可以将这个命令的执行结果通过管道传送给 tail 命令,因为你只需要找到列在最后的几个大对象。

$ git verify-pack -v .git/objects/pack/pack-29…69.idx \
| sort -k 3 -n \
| tail -3
dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 22044 5792 4977696
82c99a3e86bb1267b236a4b6eff7868d97489af1 blob 4975916 4976258 1438

你可以看到这个大对象出现在返回结果的最底部:占用 5MB 空间。 为了找出具体是哪个文件,可以使用 rev-list 命令,我们在 指定特殊的提交信息格式 中曾提到过。 如果你传递 --objects 参数给 rev-list 命令,它就会列出所有提交的 SHA-1、数据对象的 SHA-1 和与它们相关联的文件路径。 可以使用以下命令来找出你的数据对象的名字:

$ git rev-list --objects --all | grep 82c99a3
82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz

现在,你只需要从过去所有的树中移除这个文件。 使用以下命令可以轻松地查看哪些提交对这个文件产生改动:

$ git log --oneline --branches -- git.tgz
dadf725 oops - removed large tarball
7b30847 add git tarball

现在,你必须重写 7b30847 提交之后的所有提交来从 Git 历史中完全移除这个文件。 为了执行这个操作,我们要使用 filter-branch 命令,这个命令在 重写历史 中也使用过:

$ git filter-branch --index-filter \
'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz'
Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2)
Ref 'refs/heads/master' was rewritten

--index-filter 选项类似于在 重写历史 中提到的的 --tree-filter 选项,不过这个选项并不会让命令将修改在硬盘上检出的文件,而只是修改在暂存区或索引中的文件。

你必须使用 git rm --cached 命令来移除文件,而不是通过类似 rm file 的命令 - 因为你需要从索引中移除它,而不是磁盘中。 还有一个原因是速度 - Git 在运行过滤器时,并不会检出每个修订版本到磁盘中,所以这个过程会非常快。 如果愿意的话,你也可以通过 --tree-filter 选项来完成同样的任务。git rm 命令的 --ignore-unmatch 选项告诉命令:如果尝试删除的模式不存在时,不提示错误。 最后,使用 filter-branch 选项来重写自 7b30847 提交以来的历史,也就是这个问题产生的地方。 否则,这个命令会从最旧的提交开始,这将会花费许多不必要的时间。

你的历史中将不再包含对那个文件的引用。 不过,你的引用日志和你在 .git/refs/original 通过 filter-branch 选项添加的新引用中还存有对这个文件的引用,所以你必须移除它们然后重新打包数据库。 在重新打包前需要移除任何包含指向那些旧提交的指针的文件:

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 1), reused 12 (delta 0)

让我们看看你省了多少空间。

$ git count-objects -v
count: 11
size: 4904
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0

打包的仓库大小下降到了 8K,比 5MB 好很多。 可以从 size 的值看出,这个大文件还在你的松散对象中,并没有消失;但是它不会在推送或接下来的克隆中出现,这才是最重要的。 如果真的想要删除它,可以通过有 --expire 选项的 git prune 命令来完全地移除那个对象:

$ git prune --expire now
$ git count-objects -v
count: 0
size: 0
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0

环境变量

Git 总是在一个 bash shell 中运行,并借助一些 shell 环境变量来决定它的运行方式。 有时候,知道它们是什么以及它们如何让 Git 按照你想要的方式去运行会很有用。 这里不会列出所有的 Git 环境变量,但我们会涉及最有的那部分。

环境变量

Git 总是在一个 bash shell 中运行,并借助一些 shell 环境变量来决定它的运行方式。 有时候,知道它们是什么以及它们如何让 Git 按照你想要的方式去运行会很有用。 这里不会列出所有的 Git 环境变量,但我们会涉及最有的那部分。

全局行为

像通常的程序一样,Git 的常规行为依赖于环境变量。

GIT_EXEC_PATH 决定 Git 到哪找它的子程序 (像 git-commitgit-diff 等等)。 你可以用 git --exec-path 来查看当前设置。

通常不会考虑修改 HOME 这个变量(太多其它东西都依赖它),这是 Git 查找全局配置文件的地方。 如果你想要一个包括全局配置的真正的便携版 Git, 你可以在便携版 Git 的 shell 配置中覆盖 HOME 设置。

PREFIX 也类似,除了用于系统级别的配置。 Git 在 $PREFIX/etc/gitconfig 查找此文件。

如果设置了 GIT_CONFIG_NOSYSTEM,就禁用系统级别的配置文件。 这在系统配置影响了你的命令,而你又无权限修改的时候很有用。

GIT_PAGER 控制在命令行上显示多页输出的程序。 如果这个没有设置,就会用 PAGER 。

GIT_EDITOR 当用户需要编辑一些文本(比如提交信息)时, Git 会启动这个编辑器。 如果没设置,就会用 EDITOR 。

版本库位置

Git 用了几个变量来确定它如何与当前版本库交互。

GIT_DIR 是 .git 目录的位置。 如果这个没有设置, Git 会按照目录树逐层向上查找 .git 目录,直到到达 ~ 或 /

GIT_CEILING_DIRECTORIES 控制查找 .git 目录的行为。 如果你访问加载很慢的目录(如那些磁带机上的或通过网络连接访问的),你可能会想让 Git 早点停止尝试,尤其是 shell 构建时调用了 Git 。

GIT_WORK_TREE 是非空版本库的工作目录的根路径。 如果没指定,就使用 $GIT_DIR 的父目录。

GIT_INDEX_FILE 是索引文件的路径(只有非空版本库有)。

GIT_OBJECT_DIRECTORY 用来指定 .git/objects 目录的位置。

GIT_ALTERNATE_OBJECT_DIRECTORIES 一个冒号分割的列表 (格式类似 /dir/one:/dir/two:…) 用来告诉 Git 到哪里去找不在 GIT_OBJECT_DIRECTORY 目录中的对象。 如果你有很多项目有相同内容的大文件,这个可以用来避免存储过多备份。

路径规则

所谓 “pathspec” 是指你在 Git 中如何指定路径, 包括通配符的使用。 它们会在 .gitignore 文件中用到,命令行里也会用到 (git add *.c)。

GIT_GLOB_PATHSPECS and GIT_NOGLOB_PATHSPECS 控制通配符在路径规则中的默认行为。 如果 GIT_GLOB_PATHSPECS 设置为 1, 通配符表现为通配符(这是默认设置); 如果 GIT_NOGLOB_PATHSPECS 设置为 1,通配符仅匹配字面。意思是 *.c 只会匹配 文件名是 “*.c” 的文件, 而不是以 .c 结尾的文件。 你可以在各个路径规格中用 :(glob) 或 :(literal) 开头来覆盖这个配置,如 :(glob)*.c 。

GIT_LITERAL_PATHSPECS 禁用上面的两种行为;通配符将不能用,前缀覆盖也不能用。

GIT_ICASE_PATHSPECS 让所有的路径规格忽略大小写。

提交

Git 提交对象的创建通常最后是由 git-commit-tree 来完成, git-commit-tree 用这些环境变量作主要的信息源。 仅当这些值不存在才回退到预置的值。

GIT_AUTHOR_NAME 是 “author” 字段的可读的名字。

GIT_AUTHOR_EMAIL 是 “author” 字段的邮件。

GIT_AUTHOR_DATE 是 “author” 字段的时间戳。

GIT_COMMITTER_NAME 是 “committer” 字段的可读的名字。

GIT_COMMITTER_EMAIL 是 “committer” 字段的邮件。

GIT_COMMITTER_DATE 是 “committer” 字段的时间戳。

如果 user.email 没有配置, 就会用到 EMAIL 指定的邮件地址。 如果 这个 也没有设置, Git 继续回退使用系统用户和主机名。

网络

Git 使用 curl 库通过 HTTP来完成网络操作, 所以 GIT_CURL_VERBOSE 告诉 Git 显示所有由那个库产生的消息。 这跟在命令行执行 curl -v 差不多。

GIT_SSL_NO_VERIFY 告诉 Git 不用验证 SSL 证书。 这在有些时候是需要的, 例如你用一个自己签名的证书通过 HTTPS 来提供 Git 服务, 或者你正在搭建 Git 服务器,还没有安装完全的证书。

如果 Git 操作在网速低于 GIT_HTTP_LOW_SPEED_LIMIT 字节/秒,并且持续 GIT_HTTP_LOW_SPEED_TIME 秒以上的时间,Git 会终止那个操作。 这些值会覆盖 http.lowSpeedLimit 和 http.lowSpeedTime 配置的值。

GIT_HTTP_USER_AGENT 设置 Git 在通过 HTTP 通讯时用到的 user-agent。 默认值类似于 git/2.0.0

比较和合并

GIT_DIFF_OPTS 这个有点起错名字了。 有效值仅支持 -u<n> 或 --unified=<n>,用来控制在 git diff 命令中显示的内容行数。

GIT_EXTERNAL_DIFF 用来覆盖 diff.external 配置的值。 如果设置了这个值, 当执行 git diff时,Git 会调用该程序。

GIT_DIFF_PATH_COUNTER 和 GIT_DIFF_PATH_TOTAL 对于 GIT_EXTERNAL_DIFF 或 diff.external 指定的程序有用。 前者表示在一系列文件中哪个是被比较的(从 1 开始),后者表示每批文件的总数。

GIT_MERGE_VERBOSITY 控制递归合并策略的输出。 允许的值有下面这些:

  • 0 什么都不输出,除了可能会有一个错误信息。

  • 1 只显示冲突。

  • 2 还显示文件改变。

  • 3 显示因为没有改变被跳过的文件。

  • 4 显示处理的所有路径。

  • 5 显示详细的调试信息。

默认值是 2。

调试

想 真正地 知道 Git 正在做什么? Git 内置了相当完整的跟踪信息,你需要做的就是把它们打开。 这些变量的可用值如下:

  • “true”, “1”, 或 “2” – 跟踪类别写到标准错误输出。

  • 以 / 开头的绝对路径 – 跟踪输出会被写到那个文件。

GIT_TRACE 控制常规跟踪,它并不适用于特殊情况。 它跟踪的范围包括别名的展开和其他子程序的委托。

$ GIT_TRACE=true git lga
20:12:49.877982 git.c:554 trace: exec: 'git-lga'
20:12:49.878369 run-command.c:341 trace: run_command: 'git-lga'
20:12:49.879529 git.c:282 trace: alias expansion: lga => 'log' '--graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all'
20:12:49.879885 git.c:349 trace: built-in: git 'log' '--graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all'
20:12:49.899217 run-command.c:341 trace: run_command: 'less'
20:12:49.899675 run-command.c:192 trace: exec: 'less'

GIT_TRACE_PACK_ACCESS 控制访问打包文件的跟踪信息。 第一个字段是被访问的打包文件,第二个是文件的偏移量:

$ GIT_TRACE_PACK_ACCESS=true git status
20:10:12.081397 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 12
20:10:12.081886 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 34662
20:10:12.082115 sha1_file.c:2088 .git/objects/pack/pack-c3fa...291e.pack 35175
# […]
20:10:12.087398 sha1_file.c:2088 .git/objects/pack/pack-e80e...e3d2.pack 56914983
20:10:12.087419 sha1_file.c:2088 .git/objects/pack/pack-e80e...e3d2.pack 14303666
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

GIT_TRACE_PACKET 打开网络操作包级别的跟踪信息。

$ GIT_TRACE_PACKET=true git ls-remote origin
20:15:14.867043 pkt-line.c:46 packet: git< # service=git-upload-pack
20:15:14.867071 pkt-line.c:46 packet: git< 0000
20:15:14.867079 pkt-line.c:46 packet: git< 97b8860c071898d9e162678ea1035a8ced2f8b1f HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master agent=git/2.0.4
20:15:14.867088 pkt-line.c:46 packet: git< 0f20ae29889d61f2e93ae00fd34f1cdb53285702 refs/heads/ab/add-interactive-show-diff-func-name
20:15:14.867094 pkt-line.c:46 packet: git< 36dc827bc9d17f80ed4f326de21247a5d1341fbc refs/heads/ah/doc-gitk-config
# […]

GIT_TRACE_PERFORMANCE 控制性能数据的日志打印。 输出显示了每个 Git 命令调用花费的时间。

$ GIT_TRACE_PERFORMANCE=true git gc
20:18:19.499676 trace.c:414 performance: 0.374835000 s: git command: 'git' 'pack-refs' '--all' '--prune'
20:18:19.845585 trace.c:414 performance: 0.343020000 s: git command: 'git' 'reflog' 'expire' '--all'
Counting objects: 170994, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (43413/43413), done.
Writing objects: 100% (170994/170994), done.
Total 170994 (delta 126176), reused 170524 (delta 125706)
20:18:23.567927 trace.c:414 performance: 3.715349000 s: git command: 'git' 'pack-objects' '--keep-true-parents' '--honor-pack-keep' '--non-empty' '--all' '--reflog' '--unpack-unreachable=2.weeks.ago' '--local' '--delta-base-offset' '.git/objects/pack/.tmp-49190-pack'
20:18:23.584728 trace.c:414 performance: 0.000910000 s: git command: 'git' 'prune-packed'
20:18:23.605218 trace.c:414 performance: 0.017972000 s: git command: 'git' 'update-server-info'
20:18:23.606342 trace.c:414 performance: 3.756312000 s: git command: 'git' 'repack' '-d' '-l' '-A' '--unpack-unreachable=2.weeks.ago'
Checking connectivity: 170994, done.
20:18:25.225424 trace.c:414 performance: 1.616423000 s: git command: 'git' 'prune' '--expire' '2.weeks.ago'
20:18:25.232403 trace.c:414 performance: 0.001051000 s: git command: 'git' 'rerere' 'gc'
20:18:25.233159 trace.c:414 performance: 6.112217000 s: git command: 'git' 'gc'

GIT_TRACE_SETUP 显示 Git 发现的关于版本库和交互环境的信息。

$ GIT_TRACE_SETUP=true git status
20:19:47.086765 trace.c:315 setup: git_dir: .git
20:19:47.087184 trace.c:316 setup: worktree: /Users/ben/src/git
20:19:47.087191 trace.c:317 setup: cwd: /Users/ben/src/git
20:19:47.087194 trace.c:318 setup: prefix: (null)
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

其它

如果指定了 GIT_SSH, Git 连接 SSH 主机时会用指定的程序代替 ssh 。 它会被用 $GIT_SSH [username@]host [-p <port>] <command> 的命令方式调用。 这不是配置定制 ssh 调用方式的最简单的方法; 它不支持额外的命令行参数, 所以你必须写一个封装脚本然后让 GIT_SSH 指向它。 可能用 ~/.ssh/config 会更简单。

GIT_ASKPASS 覆盖了 core.askpass 配置。 这是 Git 需要向用户请求验证时用到的程序,它接受一个文本提示作为命令行参数,并在 stdout 中返回应答。 (查看 凭证存储_ 访问更多相关内容)

GIT_NAMESPACE 控制有命令空间的引用的访问,与 --namespace 标志是相同的。 这主要在服务器端有用, 如果你想在一个版本库中存储单个版本库的多个 fork, 只要保持引用是隔离的就可以。

GIT_FLUSH 强制 Git 在向标准输出增量写入时使用没有缓存的 I/O。 设置为 1 让 Git 刷新更多, 设置为 0 则使所有的输出被缓存。 默认值(若此变量未设置)是根据活动和输出模式的不同选择合适的缓存方案。

GIT_REFLOG_ACTION 让你可以指定描述性的文字写到 reflog 中。 这有个例子:

$ GIT_REFLOG_ACTION="my action" git commit --allow-empty -m 'my message'
[master 9e3d55a] my message
$ git reflog -1
9e3d55a HEAD@{0}: my action: my message

总结

现在,你应该相当了解 Git 在背后都做了些什么工作,并且在一定程度上也知道了 Git 是如何实现的。 本章讨论了很多底层命令,这些命令比我们在本书其余部分学到的高层命令来得更原始,也更简洁。 从底层了解 Git 的工作原理有助于更好地理解 Git 在内部是如何运作的,也方便你能够针对特定的工作流写出自己的工具和脚本。

作为一套内容寻址文件系统,Git 不仅仅是一个版本控制系统,它同时是一个非常强大且易用的工具。 我们希望你可以借助新学到的 Git 内部原理相关知识来实现出自己的应用,并且以更高级、更得心应手的方式来驾驭 Git。

上一篇:PostgreSQL 常用语句


下一篇:MyBatis从入门到精通(第5章):5.4 Example 介绍