.git目录
使用Git作为一个版本库,管理代码版本信息和历史记录。在一个工作区当中,我们可以通过ctrl+z、ctrl+y完成内存历史版本的更替,git是如何做到分支管理、版本管理的呢?这就涉及到了git工作空间里隐藏的.git目录,git通过这个隐藏目录实现了分支、版本的状态记录以及切换。
概念
在介绍git如何做到分支切换、版本管理之前,首先要介绍下git对象相关的概念。对象库是Git版本库实现的核心。它包含原始数据文件和所有日志消息、作者信息、日期、以及其他用来重建项目任意版本或分支的信息。Git放在对象库里的对象只有4种类型:块(blob)、目录树(tree)、提交(commit)和标签(tag)。就是由这4种对象构成Git高层数据结构的基础。
块 - blob
文件的每一个版本表示为一个块(blob)。一个blob保存一个文件的数据,但不包含任何关于这个文件的元数据,甚至连文件名也没有。
目录树 - tree
一个目录树对象代表一层目录信息,它记录blob标识符、路径名和在一个目录里所有文件的一些元数据。
提交 - commit
一个提交对象保存版本库中一次变化的元数据,包括作者、提交者、提交日期和日志消息。每一个提交对象指向一个目录树对象,而这个目录树对象在一张完整的快照中捕获提交时版本的状态。
标签 - tag
一个标签对象分配一个任意的且人类可读的名字给一个特定对象,通常是一个提交对象。
操作.git目录
.git目录介绍
新建一个目录作为git仓库,通过git init初始化git仓库。可以看到当前版本库里面是有.git目录,里面包含HEAD,config,description和branches、hooks、info、objects、refs五个文件夹。
1 | D:\ |
2 | λ mkdir gittest |
3 | D:\ |
4 | λ cd gittest\ |
5 | D:\gittest |
6 | λ git init |
7 | Initialized empty Git repository in D:/gittest/.git/ |
8 | D:\gittest (master -> origin) |
9 | λ ls -al |
10 | total 12 |
11 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 ./ |
12 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 ../ |
13 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 .git/ |
进入.git目录可以看到里面这个隐藏目录里面控制版本以及各类元属性信息,如下:
1 | λ cd .git\ |
2 | D:\gittest\.git (master -> origin) |
3 | λ ls -al |
4 | total 11 |
5 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 ./ |
6 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 ../ |
7 | -rw-r--r-- 1 zys 197609 23 May 26 16:59 HEAD |
8 | -rw-r--r-- 1 zys 197609 157 May 26 16:59 config |
9 | -rw-r--r-- 1 zys 197609 73 May 26 16:59 description |
10 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 hooks/ |
11 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 info/ |
12 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 objects/ |
13 | drwxr-xr-x 1 zys 197609 0 May 26 16:59 refs/ |
14 | D:\gittest\.git (master -> origin) |
15 | λ tree |
16 | D:. |
17 | ├─hooks |
18 | ├─info |
19 | ├─objects |
20 | │ ├─info |
21 | │ └─pack |
22 | └─refs |
23 | ├─heads |
24 | └─tags |
1,HEAD文件是当前分支的指向,比如当前分支指向master,则存储了对master的引用。
2,config文件存储了一些配置信息,属于配置文件。此处不细细描述。
3,hooks/存储了一些钩子脚本,暂时不表。
4,info文件夹是全局性排除文件,它和.gitignore是互补的。里面就一个exclude文件。
5,objects文件里面存放了所有的数据。
6,refs文件存放了提交对象的指针。
实践
HEAD存放了当前分支指向,当我们主动修改HEAD文件的指向,也是相当于切换分支checkout branch-name。
1 | D:\gittest\.git (master -> origin) |
2 | λ cat HEAD |
3 | ref: refs/heads/master |
4 | #修改HEAD内容完成切换分支功能 |
5 | D:\gittest\.git (develop -> origin) |
6 | λ echo 'ref: refs/heads/develop' > HEAD |
7 | D:\gittest\.git (develop -> origin) |
目前objects、refs目录是空的,我们在版本仓库里面添加一个文件并做一次add操作,看下这两个目录的变化。
1 | D:\gittest (master -> origin) |
2 | λ echo "v1" > v1.txt |
3 | D:\gittest (master -> origin) |
4 | λ git status |
5 | On branch master |
6 | Initial commit |
7 | Untracked files: |
8 | (use "git add <file>..." to include in what will be committed) |
9 | v1.txt |
10 | nothing added to commit but untracked files present (use "git add" to track) |
11 | D:\gittest (master -> origin) |
12 | λ git add v1.txt |
13 | D:\gittest\.git (master -> origin) |
14 | λ tree |
15 | D:. |
16 | ├─hooks |
17 | ├─info |
18 | ├─objects |
19 | │ ├─aa |
20 | │ ├─info |
21 | │ └─pack |
22 | └─refs |
23 | ├─heads |
24 | └─tags |
可以看到,objects目录下面多了一个aa的文件夹。在介绍多出来的aa文件夹之前,先介绍git常用的两个命令。1,计算文件的SHA-1校验和git hash-object filename;2,查看对象(上文提到)的内容和类型git cat-file -p/-t filename。我们看一下刚才新添加的文件的SHA-1:
1 | D:\gittest (master -> origin) |
2 | λ git hash-object v1.txt |
3 | aa67cc0c2694dd2e15ebd35464e04b266b72850c |
刚才我们创建的v1.txt文件的SHA-1为aa开头的67c…,开头与objects新增的文件夹名相同。进入到新增的文件夹看下文件目录,看到文件夹下有67cc0c2694dd2e15ebd35464e04b266b72850c的文件。这个文件和上一级文件夹组成了新增文件的SHA-1的值。这就说明了,当我们add一个文件到暂存区的时候,git会创建一个对应的objects,两级目录,前两位作为objects下面的一级目录,后面的数值作为二级文件名。通过cat-file可以查看文件的类型和内容。可以看到,aa67cc0c是一个blob块类型,指向了本次添加到暂存区的内容。
1 | D:\gittest\.git\objects (master -> origin) |
2 | λ cd aa |
3 | D:\gittest\.git\objects\aa (master -> origin) |
4 | λ ls |
5 | 67cc0c2694dd2e15ebd35464e04b266b72850c |
6 | D:\gittest\.git\objects\aa (master -> origin) |
7 | λ git cat-file -t aa67cc0c |
8 | blob |
9 | D:\gittest\.git\objects\aa (master -> origin) |
10 | λ git cat-file -p aa67cc0c |
11 | "v1" |
当我们将暂存区新增的这个文件添加到本地版本库时,可以观察到.git目录又有了新的变化。发现新增了两个文件夹。 使用git log –pretty=oneline可以查看提交的commit-id。
1 | D:\gittest (master -> origin) |
2 | λ git commit -m 'addv1' |
3 | [master (root-commit) 3ed76ec] 'addv1' |
4 | 1 file changed, 1 insertion(+) |
5 | create mode 100644 v1.txt |
6 | D:\gittest (master -> origin) |
7 | λ cd .git\ |
8 | D:\gittest\.git (master -> origin) |
9 | λ ls |
10 | COMMIT_EDITMSG HEAD config description hooks/ index info/ logs/ objects/ refs/ |
11 | D:\gittest\.git (master -> origin) |
12 | λ tree |
13 | D:. |
14 | ├─hooks |
15 | ├─info |
16 | ├─logs |
17 | │ └─refs |
18 | │ └─heads |
19 | ├─objects |
20 | │ ├─09 |
21 | │ ├─3e |
22 | │ ├─aa |
23 | │ ├─info |
24 | │ └─pack |
25 | └─refs |
26 | ├─heads |
27 | └─tags |
28 | D:\gittest\.git (master -> origin) |
29 | λ git log --pretty=oneline |
30 | 3ed76ec5a633c13942cdc205d8e82a83214ed4b5 'addv1' |
31 | D:\gittest\.git (master -> origin) |
32 | λ git cat-file -t 3ed76e |
33 | commit |
34 | D:\gittest\.git (master -> origin) |
35 | λ git cat-file -p 3ed76e |
36 | tree 09cd1fcab91850c07c5ef1f641652182eba9816d |
37 | author VfEver <13146770925@126.com> 1558878862 +0800 |
38 | committer VfEver <13146770925@126.com> 1558878862 +0800 |
39 | 'addv1' |
可以看到,刚才我们提交产生的objects(commmit-id 3ed76e)是一个commmit类型,这个commit类型的内容为我们提交的信息,包括作者信息、注释信息等。里面还有一个tree类型的对象。继续展开这个tree对象:
1 | D:\gittest\.git (master -> origin) |
2 | λ git cat-file -t 09cd1fcab |
3 | tree |
4 | D:\gittest\.git (master -> origin) |
5 | λ git cat-file -p 09cd1fcab |
6 | 100644 blob aa67cc0c2694dd2e15ebd35464e04b266b72850c v1.txt |
可以看到最终tree类型对象内容为指向blob的指针,继续展开就是我们上文添加的文件内容。此时我们查看refs/heads中当前分支存储的内容,就会发现heads里面存储的就是当前分支指向的commit-id。
1 | D:\gittest\.git\refs\heads (master -> origin) |
2 | λ cat master |
3 | 3ed76ec5a633c13942cdc205d8e82a83214ed4b5 |
再联系下上文我们提到的git中关键的各类对象,blob块对象存储了文件的数据,commit提交对象存储了当前提交的元数据信息(提交者、注释等),tree树对象则记录blob的标识符路径名等文件的元数据信息。我们继续做一次commit就可以看到tree相关的信息:
1 | D:\gittest (master -> origin) |
2 | λ echo "v2" > v2.txt |
3 | D:\gittest (master -> origin) |
4 | λ git add v2.txt |
5 | D:\gittest (master -> origin) |
6 | λ git commit -m 'addv2' |
7 | [master ca52c36] 'addv2' |
8 | 1 file changed, 1 insertion(+) |
9 | create mode 100644 v2.txt |
10 | D:\gittest (master -> origin) |
11 | λ git log --pretty=oneline |
12 | ca52c3650a62d799aaa3b5608d037105fb1568c7 'addv2' |
13 | 3ed76ec5a633c13942cdc205d8e82a83214ed4b5 'addv1' |
14 | D:\gittest (master -> origin) |
15 | λ git cat-file -p ca52c36 |
16 | tree c2f43b16b1e7591fbeed8422c61cadab2aeb77ed |
17 | parent 3ed76ec5a633c13942cdc205d8e82a83214ed4b5 |
18 | author VfEver <13146770925@126.com> 1558881909 +0800 |
19 | committer VfEver <13146770925@126.com> 1558881909 +0800 |
20 | 'addv2' |
21 | D:\gittest (master -> origin) |
22 | λ git cat-file -t c2f43b16b |
23 | tree |
24 | D:\gittest (master -> origin) |
25 | λ git cat-file -p c2f43b16b |
26 | 100644 blob aa67cc0c2694dd2e15ebd35464e04b266b72850c v1.txt |
27 | 100644 blob e08f1c7873bf3e22bef69f6bc96d6fc6bc5b617d v2.txt |
通过看第二次commit-id的内容信息,可以发现有一个parent,指向我们的第一次提交,也就是上一次提交。第一次提交的时候,是没有这个parent指针的。继续查看这次新产生的tree对象的内容,则记录了两条blob对象,一条第一次add v1的blob块对象,一条第二次add v2的blob块对象。也就是tree树对象仅仅存储了块对象的元属性信息。
如此,我们可以了解到git在add、commit的时候,产生的数据信息如下: