

Git是一个内容寻址文件系统, Git常用的命令大概30个, 但总共有130多个命令(git help -a 查看)
由于 Git 最初是一套面向版本控制系统的工具集,所以它还 包含了一部分用于完成底层工作的命令。

当在一个目录使用git init时,会创建一个.git目录。这个目录包含所有git存储和操作的对象。如果想备份或复制一个版本库,只需要把这个目录拷贝到另一个项目里,.git目录内容如下
1 2 3 4 5 6 7 8
|
$ ls -F1 HEAD --- 目前被检出的分支 config --- 仓库配置文件 description --- 供GitWeb程序使用 hooks/ --- 客户端或服务端的钩子脚本 info/ --- 全局性排除文件,用以防止那些不希望被记录在.gitignore文件中的略模式 objects/ --- 存储所有数据内容 refs/ --- 存储指向数据的提交对象的指针
|
Objects
Git的核心部分是一个简单的键值对数据库,你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索该内容。我们通过hash-object来操作这一过程
1 2
|
$echo 'test content' | git hash-object -w --stdin d670460b4b4aece5915caf5c68d12f560a9fe3e4
|
这是一个 SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1,是一种密码散列函数)哈希值——一个将待存储的数据外加一个头部信息(header)一起 做 SHA-1 校验运算而得的校验和
现在我们可以查看 Git 是如何存储数据的:
1 2
|
$ find .git/objects -type f .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
|
前两个字符用于命名子目录,余下的 38 个字符 则用作文件名
可以通过cat-file命令从git那里取回数据。
1 2
|
$git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4 test content
|
我们就可以模拟对文件进行简单的版本控制
首先创建一个新文件并将其内容存入数据库:
1 2 3
|
$ echo 'version 1' > test.txt $ git hash-object -w test.txt 83baae61804e65cc73a7201a7252750c76066a30
|
接着,向文件里写入新内容,并再次将其存入数据库:
1 2 3
|
$ echo 'version 2' > test.txt $ git hash-object -w test.txt 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
|
数据库记录下了该文件的两个不同版本,当然之前我们存入的第一条内容也还在:
1 2 3 4
|
$ find .git/objects -type f .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
|
把文件内容恢复到第一个版本:
1 2 3
|
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt $ cat test.txt version 1
|
上述类型的对象,我们称之为数据对象,除了数据对象以外,用来保存文件夹和提交的对象分别为 树对象,提交对象
树对象(tree object)
它能解决文件名保存的问题,也允许我们将多个文件组织到一起。Git 本身是以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。所有内容均以树对象和数据对象的 形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了文件内容。

一个树对象包 含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及 相应的模式、类型、文件名信息。
比如 某项目当前对应的树对象可能是这样的:
1 2 3 4
|
$ git cat-file -p master^{tree} 100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README 100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile 040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
|
我们指定的文件模式为 100644 表明这是一个普通文件,其他选择包括:100755,表示一个可执行 文件;120000,表示一个符号链接。
master^{tree} 语法表示 master 分支上最新的提交所指向的树对象。 lib子目录对应的并不是一个数据对象,而是一个指针,指向另外一个数据对象
1 2
|
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
|
从概念上讲,Git 内部存储的数据有点像这样:

根据这种机制,Git根据某一时刻暂存区所表示的状态创建并记录一个对应的树对象,如此重复便可一次记录一系列树对象。
git中的文件的状态变化周期:
你工作目录下的每一个文件都不外乎这两种状态:已跟踪或未跟踪。已跟踪的文件是指那些被纳入了版
本控制的文件,在上一次快照中有它们的记录,在工作一段时间后,它们的状态可能处于未修改,已修改或已放入暂存区。

我们可以通过底层命令 update-index为一个单独文件如test.txt的首个版本创建一个暂存区。
1 2
|
$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
|
然后通过write-tree 命令将暂存区内容写入一个树对象. (git add .)
1 2 3 4 5
|
$ git write-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 $ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
|
接着我们来做一些新的更改,比如创建一个新文件,以及更改test.txt文件
1 2 3 4 5 6 7
|
$ echo 'new file' > new.txt $ git update-index --add new.txt $ echo 'version 2' > test.txt $ git hash-object -w test.txt 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a $ git update-index test.txt
|
记录下这个目录树
1 2 3 4 5 6
|
$git write-tree 0155eb4229851634a0f03eb265b69f5a2d56f341 $ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
|
当前Git内部存储数据结构大概是这样:

提交对象
目前不同的文件改动快照已经都记录下来了,若想重用这些快照,你必须记住 所有三个 SHA-1 哈希值。并且,你也完全不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这 些快照。而以上这些,正是提交对象(commit object)能为你保存的基本信息。
可以通过调用commit-tree命令创建一个提交对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话)。我们从之前创建的第一个树对象开始:
1 2
|
$ echo 'first commit' | git commit-tree d8329f fdf4fc3344e67ab068f836878b6c4951e3b15f3d
|
现在可以通过 cat-file 命令查看这个新提交对象:
1 2 3 4 5
|
$ git cat-file -p fdf4fc3 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 first commit
|
提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳);留空一行,最后是提交注释。
接着,我们将创建另两个提交对象,它们分别引用各自的上一个提交(作为其父提交对象):
1 2 3 4
|
$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3 cac0cab538b970a37ea1e769cbbde608743bc96d $ echo 'third commit' | git commit-tree 3c4e9c -p cac0cab 1a410efbd13591db07496601ebc7a059dd55cfe9
|
提交对象和树对象的关系是这样的:

没有借助任何上层命令,仅凭几个底层操作便完成了一个 Git 提交历史的创建。这就是 每次我们运行git add和git commit命令时,Git所做的实质工作——将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。这三种主要的 Git对象——数据对象、树对象、提交对象——最初均以单独文件的形式保存在 .git/objects 目录下。下面列出了目前示例目 录内的所有对象,辅以各自所保存内容的注释:
1 2 3 4 5 6 7 8 9 10 11
|
$ find .git/objects -type f .git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2 .git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3 .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2 .git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3 .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1 .git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2 .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content' .git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1 .git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt .git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit
|
而此时如果运行git log就可以看到git的提交历史了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
$ git log --stat 1a410e commit 1a410efbd13591db07496601ebc7a059dd55cfe9 Date: Fri May 22 18:15:24 2009 -0700 third commit bak/test.txt | 1 + 1 file changed, 1 insertion(+) commit cac0cab538b970a37ea1e769cbbde608743bc96d Date: Fri May 22 18:14:29 2009 -0700 second commit new.txt | 1 + test.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d Date: Fri May 22 18:09:34 2009 -0700 first commit test.txt | 1 + 1 file changed, 1 insertion(+)
|
pro git (第二版)
起底git-git内部原理
近期评论