三个 Git 问题(上)
文章目录
Table of Contents
本系列文章主要回答三个 Git 问题:
- 为什么不推荐用 Git 保存二进制大文件?
- 如果两个 Branch 修改同一个文件的同一行代码,各自 Commit 一次,在 Merge 的时候为什么不会有冲突?
- 同样是两个 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
下图是输出:
a.txt 和 b.txt 的文件内容存储在 628ccd10742baea8241c5924df992b5c019f71 和 013625030ba8dba906f756967f9e9ca394464a 文件中。
我们可以通过 git cat-file -p [hash value]来查看保存的对象值(注意需要加上文件目录得到完整的 hash 值)
git cat-file -p cc628ccd10742baea8241c5924df992b5c019f71 git cat-file -p ce013625030ba8dba906f756967f9e9ca394464a
下图是输出:
这里的 hash 值所代表的文件就是文件快照。此时,如果我们 commit 的话,会生成新的 hash 值对象。
git commmit -m 'first commit'
find .git/objects -type f
下图是输出:
这里的 18eb80fbbbf9160491c007668d5298f1e86cd40a 和 f6042cce01150551255ec1e892d04b1c129a5fbd 是新生成的对象。
我们用 git cat-file -p 来看看它具体是什么?
git cat-file -p 18eb80fbbbf9160491c007668d5298f1e86cd40a
下图是输出:
git cat-file -p f6042cce01150551255ec1e892d04b1c129a5fbd
下图是输出:
这里的 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。