Git内部原理
从根本上来讲 Git 是一个内容寻址(content-addressable)文件系统,并在此之上提供了一个版本控制系统的用户界面。
5.1 底层命令和高层命令(Plumbing and Porcelain)
由于 Git 最初是一套面向版本控制系统的工具集,而不是一个完整的、用户友好的版本控制系统,所以它还包含了一部分用于完成底层工作的命令。 这些命令被设计成能以 UNIX 命令行的风格连接在一起,抑或藉由脚本调用,来完成工作。 这部分命令一般被称作“底层(plumbing)”命令,而那些更友好的命令则被称作“高层(porcelain)”命令。
底层命令得以让你窥探 Git 内部的工作机制,也有助于说明 Git 是如何完成工作的,以及它为何如此运作。多数底层命令并不面向最终用户:它们更适合作为新命令和自定义脚本的组成部分。
当在一个新目录或已有目录执行 git init 时,Git 会创建一个 .git 目录。这个目录包含了几乎所有 Git 存储和操作的对象。如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。本章探讨的所有内容,均位于这个目录内。该目录的结构如下所示:
$ ls -F1
HEAD
config*
description
hooks/
info/
objects/
refs/
该目录下可能还会包含其他文件,不过对于一个全新的 git init 版本库,这将是你看到的默认结构。
description 文件仅供 GitWeb 程序使用,我们无需关心。
config 文件包含项目特有的配置选项。
info 目录包含一个全局性排除(global exclude)文件,用以放置那些不希望被记录在 .gitignore 文件中的忽略模式(ignored patterns)。
hooks 目录包含客户端或服务端的钩子脚本(hook scripts),在 Git 钩子 中这部分话题已被详细探讨过。
剩下的四个条目很重要:HEAD 文件、(尚待创建的)index 文件,和 objects 目录、refs 目录。 这些条目是 Git 的核心组成部分。
objects 目录存储所有数据内容;
refs 目录存储指向数据(分支)的提交对象的指针;
HEAD 文件指示目前被检出的分支;
index 文件保存暂存区信息。
cat-file
# 查看对象内容,以友好格式展示
$ git cat-file -p 9dbf
# 查看blob对象
$ git cat-file blob 9dbf5
# 查看对象类型
$ git cat-file -t 9dbf5aa
# 查看对象大小
$ git cat-file -s 9dbf5aae1
hash-object
该命令可将任意数据保存于 .git 目录,并返回相应的键值。
# 往 Git 数据库存入一些文本:
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
# 或存入文件内容
$ git hash-object -w test.txt
5bdcfc19f119febc749eef9a9551bc335cb965e2
-w 选项指示 hash-object 命令存储数据对象;若不指定此选项,则该命令仅返回对应的键值。 –stdin 选项则指示该命令从标准输入读取内容;若不指定此选项,则须在命令尾部给出待存储文件的路径。 该命令输出一个长度为 40 个字符的校验和。 这是一个 SHA-1 哈希值——一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。
cat-file
通过 cat-file 命令从 Git 那里取回数据。 这个命令简直就是一把剖析 Git 对象的瑞士军刀。
# 为cat-file 指定 -p 选项可指示该命令自动判断内容的类型,并为我们显示格式友好的内容:
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
# 利用 cat-file -t 命令,可以让 Git 告诉我们其内部存储的任何对象类型,只要给定该对象的 SHA-1 值:
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
# master^{tree} 语法表示 master 分支上最新的提交所指向的树对象。
$ git cat-file -p master^{tree}
100644 blob df7af2c382e49245443687973ceb711b2b74cb4a test.txt
update-index
update-index 为一个单独文件——我们的 test.txt 文件的首个版本——创建一个暂存区。利用该命令,可以把 test.txt 文件的首个版本人为地加入一个新的暂存区。必须为上述命令指定 --add 选项,因为此前该文件并不在暂存区中(我们甚至都还没来得及创建一个暂存区呢,新创建的文件必须加--add);同样必需的还有--cacheinfo 选项,因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下(echo 'test content' | git hash-object -w --stdin)。 同时,需要指定文件模式、SHA-1 与文件名:
$ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
本例中,我们指定的文件模式为 100644,表明这是一个普通文件。 其他选择包括:100755,表示一个可执行文件;120000,表示一个符号链接。 这里的文件模式参考了常见的 UNIX 文件模式,但远没那么灵活——上述三种模式即是 Git 文件(即数据对象)的所有合法模式(当然,还有其他一些模式,但用于目录项和子模块)。
write-tree
通过 write-tree 命令将暂存区内容写入一个树对象。 此处无需指定 -w 选项——如果某个树对象此前并不存在的话,当调用 write-tree 命令时,它会根据当前暂存区状态自动创建一个新的树对象:
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
# 不妨验证一下它确实是一个树对象:
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
read-tree
通过调用read-tree 命令,可以把树对象读入暂存区。 本例中,可以通过对 read-tree 指定 –prefix 选项,将一个已有的树对象作为子树读入暂存区:
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
commit-tree
commit-tree 命令创建一个提交对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话 -p )。
# 不指定父对象
$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
# 指定父对象
$ echo 'second commit' | git commit-tree f473224 -p 02b254 2ba362588076f98b81bc960011a8ae89bb2da1ee
update-ref
不提倡直接编辑引用文件。 如果想更新某个引用:
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
symbolic-ref
不提倡手动编辑HEAD文件,更安全的命令来完成此事:symbolic-ref。 借此命令查看 HEAD 引用对应的值:
$ git symbolic-ref HEAD
refs/heads/master
同样可以设置 HEAD 引用的值:
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test
不能把符号引用设置为一个不符合引用格式的值:
$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/
gc
将已提交对象打包成一个称为“包文件(packfile)”的二进制文件,以节省空间和提高效率。
git gc
verify-pack
# 查看已打包的内容:
$ git verify-pack -v .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
5.2 Git对象(Git Objects)
5.2.1 向Git数据库存储和取出数据(add content to Git and pull it back out)
Git 的核心部分是一个简单的键值对数据库(key-value data store)。 你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索(retrieve)该内容。
# 首先,初始化一个新的 Git 版本库,并确认objects目录为空:
$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
# 可以看到Git对objects目录进行了初始化,并创建了pack和info子目录,但均为空。接着,往Git数据库存入一些文本:
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
# 查看 Git 是如何存储数据的
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# 可以在 objects 目录下看到一个文件。 这就是开始时 Git 存储内容的方式——一个文件对应一条内容,以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。 校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。从 Git 那里取回数据:
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
# 也可以对文件进行存取操作
# 创建新文件
$ echo 'version1' > test.txt
# 存入Git数据库
$ git hash-object -w test.txt
5bdcfc19f119febc749eef9a9551bc335cb965e2
# 查看git对象
$ find .git/objects/ -type f
.git/objects/20/74cc357a77a3eb55569b696980960afb750bef
.git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2
# 更新文件并存入Git数据库
$ echo 'version2' > test.txt
$ git hash-object -w test.txt
df7af2c382e49245443687973ceb711b2b74cb4a
# 查看git对象
$ find .git/objects/ -type f
.git/objects/20/74cc357a77a3eb55569b696980960afb750bef
.git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2
.git/objects/df/7af2c382e49245443687973ceb711b2b74cb4a
# 可以把文件内容恢复到第一个版本
$ git cat-file -p 5bdcfc19f119febc749eef9a9551bc335cb965e2 > test.txt
$ cat test.txt
version1
# 或再恢复到第二个版本
$ git cat-file -p df7af2c382e49245443687973ceb711b2b74cb4a > test.txt
$ cat test.txt
version2
5.2.2 树对象(Tree Objects)
什么是树对象?
树对象(tree object),它能解决文件名保存的问题,也允许我们将多个文件组织到一起。Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。所有内容均以树对象和数据对象的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了 inodes 或文件内容。一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。
![[Git-简化版Git数据模型.png]]
查看树对象
# 某项目对应的树对象:
$ git cat-file -p master^{tree}
040000 tree 409a63f61717e9d9a17a449501f00834a8f2bdc4 d1
100644 blob df7af2c382e49245443687973ceb711b2b74cb4a test.txt
# d1子目录(所对应的那条树对象记录)并不是一个数据对象,而是一个指针,其指向的是另一个树对象:
$ git cat-file -p 409a63f61717e9d9a17a449501f00834a8f2bdc4
100644 blob 87fb2a442f64967b96b3c2a79c8c8e7a457f8666 d1t.txt
创建树对象
# 新建test.md,V1版本
$ echo 111 > test.md
# 写入Git数据库
$ git hash-object -w test.md
58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
# 查看Git数据对象
$ find .git/objects/ -type f
.git/objects/58/c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
# 更新test.md V2版本
$ echo 222 > test.md
# 写入Git数据库
$ git hash-object -w test.md
c200906efd24ec5e783bee7f23b5d7c941b0c12c
# 查看Git数据对象
$ find .git/objects/ -type f
.git/objects/58/c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
.git/objects/c2/00906efd24ec5e783bee7f23b5d7c941b0c12c
# 将V1版本的test.md添加到暂存区
$ git update-index --add --cacheinfo 100644 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c test.md
# 此时查看项目树对象,无test.md
$ git cat-file -p master^{tree}
040000 tree 409a63f61717e9d9a17a449501f00834a8f2bdc4 d1
100644 blob df7af2c382e49245443687973ceb711b2b74cb4a test.txt
# 根据此时的暂存区状态创建树对象
$ git write-tree
36777f2a6488f543b77ec1e49d20a32af668e554
# 此时查看项目树对象,仍无无test.md,因未提交
$ git cat-file -p master^{tree}
040000 tree 409a63f61717e9d9a17a449501f00834a8f2bdc4 d1
100644 blob df7af2c382e49245443687973ceb711b2b74cb4a test.txt
# 但根据write-tree返回值可以查到test.md
$ git cat-file -p 36777f2a6488f543b77ec1e49d20a32af668e554
040000 tree 409a63f61717e9d9a17a449501f00834a8f2bdc4 d1
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c test.md
100644 blob df7af2c382e49245443687973ceb711b2b74cb4a test.txt
# 查看write-tree返回值类型是tree
$ git cat-file -t 36777f2a6488f543b77ec1e49d20a32af668e554
tree
# 将暂存区状态提交
$ git commit -m "提交test.md v1"
[master ff1171f] 提交test.md v1
1 file changed, 1 insertion(+)
create mode 100644 test.md
# 此时查看项目树对象,有test.md
$ git cat-file -p master^{tree}
040000 tree 409a63f61717e9d9a17a449501f00834a8f2bdc4 d1
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c test.md
100644 blob df7af2c382e49245443687973ceb711b2b74cb4a test.txt
你可以将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录。 通过调用read-tree 命令,可以把树对象读入暂存区。
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
如果基于这个新的树对象创建一个工作目录,你会发现工作目录的根目录包含两个文件以及一个名为 bak的子目录,该子目录包含 test.txt 文件的第一个版本。
5.2.3 提交对象(Commit Objects)
# 根据树对象创建提交对象
$ echo 'first c' | git commit-tree df000d
02b254282e9ec2ab61461b90db56d24ea5d82ce0
# 查看提交对象内容
$ git cat-file -p 02b2
tree df000d3eda8ddc32f967d3952c46e3323f0cf143
author jacob <jacobap@163.com> 1734176098 +0800
committer jacob <jacobap@163.com> 1734176098 +0800
first c
引用上一个提交(作为其父提交对象 -p ):
$ echo 'second commit' | git commit-tree f473224 -p 02b254 2ba362588076f98b81bc960011a8ae89bb2da1ee
$ git cat-file -p 2ba3
tree f4732244ad37e6d4030b053545151e5a806d7a88
parent 02b254282e9ec2ab61461b90db56d24ea5d82ce0
author jacob <jacobap@163.com> 1734176456 +0800
committer jacob <jacobap@163.com> 1734176456 +0800
second commit
# 根据第三各树对象进行提交,以第二次提交对象为父对象
$ echo 'third commit' | git commit-tree 63dfea51 -p 2ba3 88fc4f59f5d612cbd094865c85da76dbe302b0de

5.2.4 对象存储(Object Storage)
前文曾提及,在存储内容时,会有个头部信息一并被保存。 让我们略花些时间来看看 Git 是如何存储其对象的。 通过在 Ruby 脚本语言中交互式地演示,你将看到一个数据对象——本例中是字符串“what is up, doc?”——是如何被存储的。
可以通过 irb 命令启动 Ruby 的交互模式:
$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"
Git 以对象类型作为开头来构造一个头部信息,本例中是一个“blob”字符串。 接着 Git 会添加一个空格,随后是数据内容的长度,最后是一个空字节(null byte):
>> header = "blob #{content.length}\0"
=> "blob 16\u0000"
Git 会将上述头部信息和原始数据拼接起来,并计算出这条新内容的 SHA-1 校验和。 在 Ruby 中可以这样计算 SHA-1 值——先通过 require 命令导入 SHA-1 digest 库,然后对目标字符串调用Digest::SHA1.hexdigest():
>> store = header + content
=> "blob 16\u0000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
Git 会通过 zlib 压缩这条新内容。在 Ruby 中可以借助 zlib 库做到这一点。 先导入相应的库,然后对目标内容调用 Zlib::Deflate.deflate():
>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"
最后,需要将这条经由 zlib 压缩的内容写入磁盘上的某个对象。 要先确定待写入对象的路径(SHA-1 值的前两个字符作为子目录名称,后 38 个字符则作为子目录内文件的名称)。 如果该子目录不存在,可以通过 Ruby 中的 FileUtils.mkdir_p() 函数来创建它。 接着,通过 File.open() 打开这个文件。最后,对上一步中得到的文件句柄调用 write() 函数,以向目标文件写入之前那条 zlib 压缩过的内容:
>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32
就是这样——你已创建了一个有效的 Git 数据对象。 所有的 Git 对象均以这种方式存储,区别仅在于类型标识——另两种对象类型的头部信息以字符串“commit”或“tree”开头,而不是“blob”。 另外,虽然数据对象的内容几乎可以是任何东西,但提交对象和树对象的内容却有各自固定的格式。
5.3 Git引用(Git References)
什么是Git引用
SHA-1值复杂难记,为了简化,使用一个文件来保存SHA-1值,并给文件起一个简单好记的名字,然后用这个名字指针来代替原始的SHA-1值。这样的文件被称为:引用(references,或缩写为refs),存储在.git/refs目录。
初始目录结构:
$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f
分支引用
创建一个新引用来帮助记忆最新提交所在的位置,从技术上讲我们只需简单地做如下操作:
$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master
现在,你就可以在 Git 命令中使用这个刚创建的新引用来代替 SHA-1 值了:
$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
不提倡直接编辑引用文件。 如果想更新某个引用:
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
这基本就是 Git 分支的本质:一个指向某一系列提交之首的指针或引用。 若想在第二个提交上创建一个分支,可以这么做:
$ git update-ref refs/heads/test cac0ca
这个分支将只包含从第二个提交开始往前追溯的记录:
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

当运行类似于 git branch (branchname) 这样的命令时,Git 实际上会运行 update-ref 命令,取得当前所在分支最新提交对应的 SHA-1 值,并将其加入你想要创建的任何新引用中。
HEAD引用(The HEAD)
HEAD 文件是一个符号引用(symbolic reference),指向目前所在的分支。 符号引用,它是一个指向其他引用的指针。
$ cat .git/HEAD
ref: refs/heads/master
如果执行 git checkout test,Git 会像这样更新 HEAD 文件:
$ cat .git/HEAD
ref: refs/heads/test
However in some rare cases the HEAD file may contain the SHA-1 value of a Git object. This happens when you checkout a tag, commit, or remote branch, which puts your repository in "detached HEAD" state.
# 查看 HEAD 引用对应的值:
$ git symbolic-ref HEAD
refs/heads/master
# 同样可以设置 HEAD 引用的值:
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test
标签引用(Tags)
标签对象(tag object)非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。 主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。 它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。
# 创建一个轻量标签(lightweight tag 固定的引用):
$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
# 创建一个附注标签(annotated tag)
$ git tag -a v1.2 2ba362588076f98b81bc960011a8ae89bb2da1ee -m 'test tag v2'
# 查看标签引用内容,是标签对象的SHA-1
$ cat .git/refs/tags/v1.2
8455d1dd81b2990ec9e2a6ed3e6874f6ba770a4e
# 查看标签对象的内容,object指向提交对象的SHA-1
$ git cat-file -p 8455d1dd81b2990ec9e2a6ed3e6874f6ba770a4e
object 2ba362588076f98b81bc960011a8ae89bb2da1ee
type commit
tag v1.2
tagger jacob <jacobap@163.com> 1734189286 +0800
test tag v2
object 条目指向我们打了标签的那个提交对象的 SHA-1 值。 另外要注意的是,标签对象并非必须指向某个提交对象;你可以对任意类型的 Git 对象打标签。例如,在 Git 源码中,项目维护者将他们的 GPG 公钥添加为一个数据对象,然后对这个对象打了一个标签。可以克隆一个 Git 版本库,然后通过执行下面的命令来在这个版本库中查看上述公钥:
$ git cat-file blob junio-gpg-pub
Linux 内核版本库同样有一个不指向提交对象的标签对象——首个被创建的标签对象所指向的是最初被引入版本库的那份内核源码所对应的树对象。
远程引用(Remotes)
如果你添加了一个远程版本库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存在 refs/remotes 目录下。 例如,你可以添加一个叫做 origin 的远程版本库,然后把 master 分支推送上去:
$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
a11bef0..ca82a6d master -> master
此时,如果查看 refs/remotes/origin/master 文件,可以发现 origin 远程版本库的 master 分支所对应的 SHA-1 值,就是最近一次与服务器通信时本地 master 分支所对应的 SHA-1 值:
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
远程引用和分支(位于 refs/heads 目录下的引用)之间最主要的区别在于,远程引用是只读的。 虽然可以 git checkout 到某个远程引用,但是 Git 并不会将 HEAD 引用指向该远程引用。因此,你永远不能通过 commit 命令来更新远程引用。 Git 将这些远程引用作为记录远程服务器上各分支最后已知位置状态的书签来管理。
普通引用
和普通的面向对象编程一样,新建数据对象后需要一个引用变量名。
# 新建数据对象
$ echo '123456' | git hash-object -w --stdin
9f358a4addefcab294b83e4282bfef1f9625a249
# 引用变量名(refs/others/test_tag)
$ git update-ref refs/others/test_tag 9f358a4addefcab294b83e4282bfef1f9625a249
# 查看对象内容
$ git cat-file -p refs/others/test_tag
123456
# 查看引用
$ cat .git/refs/others/test_tag
9f358a4addefcab294b83e4282bfef1f9625a249
5.4 包文件(Packfiles)
初次打包前Git向磁盘中存储对象时所使用的格式被称为“松散(loose)”对象格式。但是,Git 会时不时地将多个这些对象打包成一个称为“包文件(packfile)”的二进制文件,以节省空间和提高效率。当版本库中有太多的松散对象,或者你手动执行 git gc 命令,或者你向远程服务器执行推送时,Git 都会这样做。要看到打包过程,你可以手动执行 git gc 命令让 Git 对对象进行打包:
$ git gc
Counting objects: 18, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (18/18), done.
Total 18 (delta 3), reused 0 (delta 0)
这个时候再查看 objects 目录,你会发现大部分的对象都不见了,与此同时出现了一对新文件:
$ find .git/objects -type f
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack
未提交的对象,Git 认为它们是悬空(dangling)的,不会将它们打包进新生成的包文件中。
剩下的文件是新创建的包文件和一个索引。包文件包含了刚才从文件系统中移除的所有对象的内容。索引文件包含了包文件的偏移信息,我们通过索引文件就可以快速定位任意一个指定对象。有意思的是运行 gc命令前磁盘上的对象大小约为 22K,而这个新生成的包文件大小仅有 7K。 通过打包对象减少了 ⅔ 的磁盘占用空间。
Git 是如何做到这点的?Git 打包对象时,会查找命名及大小相近的文件,并只保存文件不同版本之间的差异内容。你可以查看包文件,观察它是如何节省空间的。
$ git verify-pack -v .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
b042a60ef7dff760008df33cee372b945b6e884e blob 22054 5799 1463
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 9 20 7262 1 \
b042a60ef7dff760008df33cee372b945b6e884e
033b4 这个数据对象(即 repo.rb 文件的第一个版本,如果你还记得的话)引用了数据对象b042a,即该文件的第二个版本。 命令输出内容的第三列显示的是各个对象在包文件中的大小,可以看到b042a 占用了 22K 空间,而 033b4 仅占用 9 字节。 同样有趣的地方在于,第二个版本完整保存了文件内容,而原始的版本反而是以差异方式保存的——这是因为大部分情况下需要快速访问文件的最新版本。
Git 时常会自动对仓库进行重新打包。也可以随时手动执行 git gc。
5.5 引用规格(Refspec)
假设你添加了这样一个远程版本库:
$ git remote add origin https://github.com/schacon/simplegit-progit
上述命令会在你的 .git/config 文件中添加一个小节,并在其中指定远程版本库的名称(origin)、URL 和一个用于fetch操作的引用规格(refspec):
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
引用规格的格式由一个可选的 + 号和紧随其后的 <src>:<dst> 组成,其中 <src> 是一个模式(pattern),代表远程版本库中的引用;<dst> 是那些远程引用在本地所对应的位置。 + 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用(tells Git to update the reference even if it isn’t a fast-forward.)。
默认情况下,引用规格由 git remote add 命令自动生成, Git 获取服务器中 refs/heads/ 下面的所有引用,并将它写入到本地的 refs/remotes/origin/ 中。 所以,如果服务器上有一个 master 分支,我们可以在本地通过下面这种方式来访问该分支上的提交记录:
$ git log origin/master
$ git log remotes/origin/master
$ git log refs/remotes/origin/master
上面的三个命令作用相同,因为 Git 会把它们都扩展成 refs/remotes/origin/master。
如果想让 Git 每次只拉取远程的 master 分支,而不是所有分支,可以把(引用规格的)fetch那一行修改为:
fetch = +refs/heads/master:refs/remotes/origin/master
如果有某些只希望被执行一次的操作,我们也可以在命令行指定引用规格。 若要将远程的 master 分支拉到本地的 origin/mymaster 分支,可以运行:
$ git fetch origin master:refs/remotes/origin/mymaster
你也可以指定多个引用规格。 在命令行中,你可以按照如下的方式拉取多个分支:
$ git fetch origin master:refs/remotes/origin/mymaster \
topic:refs/remotes/origin/topic
From git@github.com:schacon/simplegit
! [rejected] master -> origin/mymaster (non fast forward)
* [new branch] topic -> origin/topic
在这个例子中,对 master 分支的fetch操作被拒绝,因为它不是一个可以快进的引用。 我们可以通过在引用规格之前指定 + 号来覆盖该规则。
你也可以在配置文件中指定多个用于获取操作的引用规格。 如果想在每次获取时都包括 master 和experiment 分支,添加如下两行:
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/experiment:refs/remotes/origin/experiment
我们不能在模式中使用部分通配符,所以像下面这样的引用规格是不合法的:
fetch = +refs/heads/qa*:refs/remotes/origin/qa*
但我们可以使用命名空间(或目录)来达到类似目的。 假设你有一个 QA 团队,他们推送了一系列分支,同时你只想要获取 master 和 QA 团队的所有分支而不关心其他任何分支,那么可以使用如下配置:
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/qa/*:refs/remotes/origin/qa/*
如果项目的工作流很复杂,有 QA 团队推送分支、开发人员推送分支、集成团队推送并且在远程分支上展开协作,你就可以像这样(在本地)为这些分支创建各自的命名空间,非常方便。
引用规格推送
如果 QA 团队想把他们的 master 分支推送到远程服务器的 qa/master 分支上,可以运行:
$ git push origin master:refs/heads/qa/master
如果他们希望 Git 每次运行 git push origin 时都像上面这样推送,可以在他们的配置文件中添加一条push 值:
[remote "origin"]
url = https://github.com/schacon/simplegit-progit
fetch = +refs/heads/*:refs/remotes/origin/*
push = refs/heads/master:refs/heads/qa/master
删除引用
通过引用规格从远程服务器上删除引用
$ git push origin :topic
因为引用规格(的格式)是 <src>:<dst>,所以上述命令把 <src> 留空,意味着把远程版本库的 topic分支定义为空值,也就是删除它。
5.6 传输协议
Git 可以通过两种主要的方式在版本库之间传输数据:“哑(dumb)”协议和“智能(smart)”协议。
dumb协议,很少使用
很简单但效率略低,且它不能从客户端向服务端发送数据。
smart协议,常用
需要在服务端运行一个进程,而这也是 Git 的智能之处——它可以读取本地数据,理解客户端有什么和需要什么,并为它生成合适的包文件。 总共有两组进程用于传输数据,它们分别负责上传和下载数据。
上传数据
为了上传数据至远端,Git 使用 send-pack 和 receive-pack 进程。 运行在客户端上的 send-pack 进程连接到远端运行的 receive-pack 进程。
SSH
HTTP(S)
下载数据
下载数据时, 使用fetch-pack 和 upload-pack 进程。客户端启动 fetch-pack 进程,连接至远端的 upload-pack 进程,以传输数据。
5.7 维护和数据恢复
5.7.1 维护
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 文件中。
注意这个文件的最后一行,它以 ^ 开头。 这个符号表示它上一行的标签是附注标签,而以 ^ 开头那一行是附注标签指向的那个提交对象的SHA-1值。
5.7.2 数据恢复
在你使用 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 然后增加一个指向它的分支。
$ 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” 后看到你丢失的提交。 现在你可以用和之前相同的方法恢复这个提交,也就是添加一个指向这个提交的分支。
5.7.3 移除对象
10.7 Git 内部原理 – 维护与数据恢复
5.8 环境变量
全局行为
GIT_EXEC_PATH 决定 Git 到哪找它的子程序 (像 git-commit, git-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 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中文网整理。

浙公网安备 33010602011771号