分布式的Git相对于svn优势已经很明显了。本文花几分钟时间简单探讨下Git 的思想和主要的对象类型。
首先来看下Git的文件目录。某个工程目录中,执行在git init 之后,打开.git文件夹可以看到 git 目录结构如下:
1 | Git 目录结构 |
Git Objects
git 对象是git实现的基础。
Git show –help 的时候可以看到Git里面有四种对象的描述 : blobs, trees, tags, commits。这就是Git 内部主要的四种对象。
1 | git show --help |
1 | .git/objects |
上面的目录可以看到objects下面是两层目录,这么设计主要是为了是防止对象太多。其中文件的文件名是其 sha-1 后 base16 编码的字符串。
下面分别来看下四种对象:
blob:存储实际的文件 (Blob: binary large object)
blob文件只存储文件内容,对应的文件名并没有存在 blob 对象中。好处是相同内容的文件,即便拷贝多份,依然只存储一份数据 。所以blob 只和内容相关,更改文件名只是生成一个新的 tree,不会新增blob。也就是说任何人,在任何硬件环境下,相同的内容都会生成相同的 blob。
blob可以被组织成树,一次 提交(commit)就是根据更改的文件的信息生成新的树的过程,新树和老树共享相同的子树,只有变化的部分才会分叉。通过使用引用,比如 HEAD, heads/master,tags/v0.1,git 可以很方便地追踪用户关心的每一棵树的状态。
tree:存储文件的目录结构
tree,代表目录,记录文件路径 文件名。通过tree找到Blob,任何人,在任何硬件环境下,相同的目录结构,包含相同的内容,都会生成相同的 tree。
commit:存储提交信息(主要是当前的树根和上一棵树的树根)
因为commit不仅存有Tree,还存有 Author Email Date 等信息,所以每个Commit 哈希值都不同。每次commit 相当于当前工程的一个snapshot。(通过 Commit -> tree -> blob 定位对应的文件)
tag:存储版本信息,相当于对对象库中的某个 commit 显式标记了一下。
我们可以使用 git show <文件名>
命令来查看Object的类型。打开工程下面的 .git/objects
目录,由于objects下面是二级目录,所以里面的 子文件夹名字+文件名,就是一个完整的对象ID。下面是一些例子:
1 | 用来展示对象内容 |
由此可见,基本上一个commit就是一次snapshot。记录了当时的目录结构以及压缩保存的文件。同时,由于
- 每个文件一旦写入对象数据库中都是不可更改的
- 任何微小的修改,都会在数据库中形成一个新的对象
- blob不记录文件名,只要文件内容相同,就会使用同样的blob。
通过commit -> tree -> blob,我们可以找到任意一个文件,以及修改记录。
一些git命令原理也同样利用这个规则。
比如, git stash
同样以git object对象实现。stash命令实际上创建了一个新特殊commit对象,该commit对象有两个parent,一个是前面一次git commit命令生成的commit,另一个对应于保存到stage中的commit。git branch
和 git tag
也都存储了对应的commit。不同的是tag创建后其指向的commit不能变化,而branch创建后,会在提交新的commit后向前移动。