本系列文章主要回答三个 Git 问题:

  1. 为什么不推荐用 Git 保存二进制大文件?
  2. 如果两个 Branch 修改同一个文件的同一行代码,各自 Commit 一次,在 Merge 的时候为什么不会有冲突?
  3. 同样是两个 Branch 修改同一个文件的同一行代码,各自 Commit 一次,在 Rebase 的时候为何有一个 Commit 会被优化掉?

要回答好这三个问题,我们需要理解 Git 的内部工作原理。

在保存文件历史的时候,Git 跟 SVN 最大的不一样在于,它保存的不是文件的 Diff 而是整个文件的快照。

什么是文件快照

mkdir testGit
cd testGit
echo "hello" >> a.txt
echo "world" >> b.txt
git init
git add .

此时我们在 testGit 目录下面新建了一个 Git 仓库,并且新建了两个 txt 文件。此时我们可以在.git/objects 目录下面找到这两个文件。当调用完 git add .之后, Git 会对当前目录下面的所有文件进行“快照”并保存。

大概就是 a.txt 和 b.txt 经过 zlib 压缩后,使用 SHA-1 算法把文件内容和文件头生成一个 40 位的 hash 值,并且使用此 hash 值的前两个字符作为目录,后面 38 位作为文件名来保存压缩后的文件对象。

我们可以通过 find 命令来查找.git/objects 目录下面所有存储的对象:

find .git/objects -type f

下图是输出: 2015-09-12-three-git-question_find-objects-1.png

a.txt 和 b.txt 的文件内容存储在 628ccd10742baea8241c5924df992b5c019f71 和 013625030ba8dba906f756967f9e9ca394464a 文件中。

我们可以通过 git cat-file -p [hash value]来查看保存的对象值(注意需要加上文件目录得到完整的 hash 值)

git cat-file -p cc628ccd10742baea8241c5924df992b5c019f71
git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a

下图是输出: 2015-09-12-three-git-question_cat-file-1.png

这里的 hash 值所代表的文件就是文件快照。此时,如果我们 commit 的话,会生成新的 hash 值对象。

git commmit -m 'first commit'
find .git/objects -type f

下图是输出: 2015-09-12-three-git-question_cat-file-2.png

这里的 18eb80fbbbf9160491c007668d5298f1e86cd40a 和 f6042cce01150551255ec1e892d04b1c129a5fbd 是新生成的对象。

我们用 git cat-file -p 来看看它具体是什么?

git cat-file -p 18eb80fbbbf9160491c007668d5298f1e86cd40a

下图是输出: 2015-09-12-three-git-question_cat-file-3.png

git cat-file -p f6042cce01150551255ec1e892d04b1c129a5fbd

下图是输出: 2015-09-12-three-git-question_cat-tree-1.png

这里的 18eb80fbbbf9160491c007668d5298f1e86cd40a 表示的是当前目录树的 hash 值,里面包含了每个文件的权限,类型,hash code 和名字信息。

而 f6042cce01150551255ec1e892d04b1c129a5fbd 则是我们的 commit 号,也就是平常我们用 git log 得到的内容。

如果此时我们修改 a.txt 或者 b.txt 并 commit,新修改后的文件会再用 zlib 压缩后生成一个 hash code 来当作名字存储。

为什么 Git 切换分支特别快?

因此要把版本的历史 checkout 到工作区,Git 只需要把 commit 对应的目录树的所有的文件解压缩即可。

如果是 svn,则需要找到一个参考版本,然后不断地应用 Diff 才来得到相应的分支,这个速度是非常之慢的。

为什么不推荐使用 Git 保存二进制大文件

因为 Git 使用的是文件快照来保存版本历史,而二进制文件在压缩上几乎没有效果,所以,二进制文件只要有一点点修改,保存的就是整个文件内容。

所以大的二进制文件是禁止放到 Git 里面去管理的。那么多大才算大呢?一般的标准是单个二进制文件的大小不要超过 100kb。

参考文献