深入理解Git的实现原理
- Git是什么
- Git能够解决哪些问题
- Git的实现原理
请注意,本文的阐述逻辑是:Git是什么——>Git要解决的根本问题是什么——>git是如何解决这些问题的。
另一个个问题,为什么说Git是“分布式”版本控制系统呢?
这里的“分布式”是相对于“集中式”来说的。把数据集中保存在服务器节点,所有的客户节点都从服务节点获取数据的版本控制系统叫做集中式版本控制系统,比如svn就是典型的集中式版本控制系统。
与之相对,Git的数据不止保存在服务器上,同时也完整的保存在本地计算机上,所以我们称Git为分布式版本控制系统。
Git的这种特性带来许多便利,比如你可以在完全离线的情况下使用Git,随时随地提交项目更新,而且你不必为单点故障过分担心,即使服务器宕机或数据损毁,也可以用任何一个节点上的数据恢复项目,因为每一个开发节点都保存着完整的项目文件镜像。
- 我们可以为每一次变更提交版本更新并且备注更新的内容;
- 我们可以在项目的各个历史版本之间自如切换;
- 我们可以一目了然的比较出两个版本之间的差异;
- 我们可以从当前的修改中撤销一些操作;
- 我们可以自如的创建分支、合并分支;
- 我们可以和多人协作开发;
- 我们可以采取自由多样的开发模式。
所以,如果问“Git能够解决哪些问题?”我们可以简单的回答:Git解决了版本控制方面的很多问题,但最核心的是它很好的解决了版本状态存储(即文件变更过程存储)的问题。
- git init 用于创建一个空的git仓库,或重置一个已存在的git仓库
- git hash-object git底层命令,用于向Git数据库中写入数据
- git cat-file git底层命令,用于查看Git数据库中数据
$ git init GitTest Initialized empty Git repository in /home/mp/Workspace/GitTest/.git/ |
$ cd GitTest
$ ls
$ find .git
.git
.git/HEAD .git/config .git/objects .git/objects/info .git/objects/pack .git/refs .git/refs/heads .git/refs/tags .git/hooks .git/hooks/commit-msg.sample .git/hooks/post-update.sample .git/hooks/update.sample .git/hooks/pre-rebase.sample .git/hooks/pre-applypatch.sample .git/hooks/fsmonitor-watchman.sample .git/hooks/applypatch-msg.sample .git/hooks/pre-receive.sample .git/hooks/pre-push.sample .git/hooks/prepare-commit-msg.sample .git/hooks/pre-commit.sample .git/info .git/info/exclude .git/branches .git/description |
$ echo "version 1" | git hash-object -w --stdin 83baae61804e65cc73a7201a7252750c76066a30 $ find .git/objects/ -type f |
$ git cat-file -t 83baa
blob
$ git cat-file -p 83baa
version 1
|
$ echo "version 1" > file.txt
$ git hash-object -w file.txt
83baae61804e65cc73a7201a7252750c76066a30
|
$ find .git/objects -type f .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 |
$ echo "version 2" > file.txt
$ git hash-object -w file.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 |
我们发现,.git/objects下多出了一个文件,这是我们新保存进数据库的file.txt。接下来,我们执行git cat-file搞清楚这两条数据的内容分别是什么。执行
$git cat-file -p 83baa
version 1
$git cat-file -p 1f7a7a
version 2
|
我们发现,file.txt的变更过程被完整的记录下来了。
$ cat file.txt
version 2 $ git cat-file -p 83baa > file.txt
$ cat file.txt
version 1 |
file.txt的内容成功恢复到了修改前的状态,变成了“version 1”。这其实就是版本回滚的实质。
- 第一,无法记录文件名的变化;
- 第二,无法记录文件夹的变化;
- 第三,记忆每一个版本对应的hash值无聊且乏味且不可能;
- 第四,无法得知文件的变更时序;
- 第五,缺少对每一次版本变化的说明。
- git update-index git底层命令,用于创建暂存区
- git ls-files --stage git底层命令,用于查看暂存区内容
- git write-tree git底层命令,用于将暂存区内容写入一个树对象
OK,万事俱备,我们将file.txt的第一个版本放入暂存区,执行
$ find .git/index find: ‘.git/index’: No such file or directory $ git update-index --add file.txt $ find .git/index .git/index $ cat .git/index DIRC[���$�;�[���$�;�A���� ���a�Ne�s� rRu vjfile.txt�݀3%A��,I� �` $ find .git/objects/ -type f .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 $ git ls-files --stage 100644 83baae61804e65cc73a7201a7252750c76066a30 0 file.txt $ git write-tree 391a4e90ba882dbc9ea93855103f6b1fa6791cf6 $ find .git/objects/ -type f .git/objects/39/1a4e90ba882dbc9ea93855103f6b1fa6791cf6 .git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a .git/objects/83/baae61804e65cc73a7201a7252750c76066a30 |
$ git cat-file -t 391a4e tree $ git cat-file -p 391a4e 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 file.txt |
83baae61804e65cc73a7201a7252750c76066a30 |
$ echo "new file" > new $ git ls-files --stage |
- 如果添加git数据库中尚未存储的数据到暂存区,则在执行update-index的时候,会同时把该数据保存到git数据库。
- 添加文件进入暂存区的操作是追加操作,之前已经加入暂存区的文件依然存在——很多人会有误区,认为变更提交之后,暂存区就清空了。
$ git cat-file -p 228e49 100644 blob 83baae61804e65cc73a7201a7252750c76066a30 file.txt 100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new |
$ mkdir new_dir $ git update-index --add new_dir error: new_dir: is a directory - add files inside instead fatal: Unable to process path new_dir |
$ echo "file in new dir" > new_dir/new $ git write-tree $ git cat-file -p 06564b |
从执行结果可见,文件夹new_dir对应一个tree对象。
至此,在git数据库中,我们可以完整的记录文件的状态、文件夹的状态;并且可以把多个文件或文件夹组织在一起,记录他们的变更过程。我们离一个完善的版本控制系统似乎已经不远了,而这一切实现起来又是如此简单——我们只是通过几个命令操作git数据库就完成了这些功能。
$ git write-tree
cb0fbcc484a3376b3e70958a05be0299e57ab495
$ git commit-tree cb0fbcc -m "first commit"
7020a97c0e792f340e00e1bb8edcbafcc4dfb60f
$ git cat-file 7020a97
tree cb0fbcc484a3376b3e70958a05be0299e57ab495
author john <john@163.com> 1537961478 +0800 committer john <john@163.com> 1537961478 +0800 first commit |
$ echo "new version" > file.txt
$ git update-index file.txt
$ git write-tree
848e967643b947124acacc3a2d6c5a13c549231c $ git commit-tree 848e96 -p 7020a97 -m "second commit" e838c8678ef789df84c2666495663060c90975d7 $ git cat-file -p e838c tree 848e967643b947124acacc3a2d6c5a13c549231c parent 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f author john <john@163.com> 1537962442 +0800 committer john <john@163.com> 1537962442 +0800 second commit |
$ echo "another version" > file.txt $ git update-index file.txt $ git write-tree 92867fcc5e0f78c195c43d1de25aa78974fa8103 $ git commit-tree 92867 -p e838c -m "third commit" 491404fa6e6f95eb14683c3c06d10ddc5f8e883f $ git cat-file -p 49140 tree 92867fcc5e0f78c195c43d1de25aa78974fa8103 parent e838c8678ef789df84c2666495663060c90975d7 author john <john@163.com> 1537963274 +0800 committer john <john@163.com> 1537963274 +0800
third commit
|
$ git log 49140 commit 491404fa6e6f95eb14683c3c06d10ddc5f8e883f Author: john <john@163.com> Date: Wed Sep 26 20:01:14 2018 +0800 third commit commit e838c8678ef789df84c2666495663060c90975d7 Author: john <john@163.com> Date: Wed Sep 26 19:47:22 2018 +0800 second commit commit 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f Author: john <john@163.com> Date: Wed Sep 26 19:31:18 2018 +0800 first commit |
git add
和 git commit
命令时, Git 所做的实质工作是将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。 这三种主要的 Git 对象——数据对象、树对象、提交对象——最初均以单独文件的形式保存在 .git/objects
目录下。$ echo "491404fa6e6f95eb14683c3c06d10ddc5f8e883f" > .git/refs/heads/master $ cat .git/refs/heads/master 491404fa6e6f95eb14683c3c06d10ddc5f8e883f |
$ git log 491404 commit 491404fa6e6f95eb14683c3c06d10ddc5f8e883f (HEAD -> master) Author: john <john@163.com> Date: Wed Sep 26 20:01:14 2018 +0800
third commit
commit e838c8678ef789df84c2666495663060c90975d7
Author: john <john@163.com> Date: Wed Sep 26 19:47:22 2018 +0800
second commit
commit 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f
Author: john <john@163.com> Date: Wed Sep 26 19:31:18 2018 +0800
first commit
$ git log master commit 491404fa6e6f95eb14683c3c06d10ddc5f8e883f (HEAD -> master) Author: john <john@163.com> Date: Wed Sep 26 20:01:14 2018 +0800
third commit
commit e838c8678ef789df84c2666495663060c90975d7
Author: john <john@163.com> Date: Wed Sep 26 19:47:22 2018 +0800
second commit
commit 7020a97c0e792f340e00e1bb8edcbafcc4dfb60f
Author: john <john@163.com> Date: Wed Sep 26 19:31:18 2018 +0800
first commit
|
$ git update-ref refs/heads/master 49140 |