Git背后的object
2016-01-21 20:05
471 查看
Git是一个版本库,它用来维护与管理项目的修订版本和历史信息。
有时候我会觉得Git像是一个时光机,它能带我穿越时空回到过去,并且改写历史,但Git的功能又不仅仅如此,它可以让你在多个空间来回穿梭,甚至可以合并两个空间。我一直觉得学习一个新的事物,首先要学习它的基础和思想,这样自己才不至于会迷失。所以在我们能穿越时空之前,有些基础但却非常东西是需要学习的,要不然就会出现像至尊宝一样的状况,拿着月光宝盒念着波若波若蜜但却无法随心所欲穿越时空。
Git维护两个主要的数据结构:对象库(object store)和索引(index)。所有这些版本库数据存放在工作目录下一个名为.git的隐藏子目录中。
首先,让我们看看一个刚init的repo里.git目录中都有些什么吧。
里面的目录都是用来干吗的我们先不用管,需要先注意的是这个objects目录。我们再添加一个event.txt看看之后这个目录会发生什么变化。
我想你已经看到了objects目录下多了些什么。那么我们再把这个event.txt提交后再看看有什么变化。
同样的,在objects目录下又多出了一些东西,那这些新增的文件都是什么呢?
首先看一下master里面现在存的是什么。
可以看到里面存了一个Sha1值,可能你已经注意到这个Sha1值就存在于objects目录下。那么打开这个object文件看看里面的存是什么。
可以看到此文件中存放的是我们执行
同样它里面也包含了一个Sha1值,它同样应了一个objects目录下的一个文件,我们再打开这个文件看看。
这次里面的内容是我所添加的文件event.txt里面的内容。至次三个新出新的object文件的内容都已经看完了,那么让我们把它们的关系串联一下,会得到下面的关系图:
Git放在对象库里的对象只有4种类型:块(blob)、目录树(tree)、提交(commit)和标签(tag)。就是由这4种对
4000
象构成Git高层数据结构的基础。
其中一个commit会包含一个tree指向,也会包含一个parent指向,首个commit没有parent;一个tree包含一个或多个blob,同样也可能包含零个或多个tree的指向;blob里面保存一个文件的数据,仅仅是数据,不包含其它的内容,不同的文件名,只要内容相同,都指向同一个blob。
此时.git/objects/目录下结构是这样的
然后,像向一步一样又提交一个C1.txt:
些时,提交图结构是这样的:
可以看到此时master已经指像了最新的commit ac3761d,而最新的commit的parent指向了上一次的commit,同时commit的tree属性指向了tree 2422ec4,对于tree 2422ec4来说它指向了两个blob。
假设此时你突然有一个想法想实践一下,但是你现在并不想在master分支上做修改,于是你建了一个名为try001的分支,等验证这个想法确实可行后你想再将其合并到master分支上。
此时如果查看.git下的目录,你可以看到./refs/heads目录下多了一个try001的文件,这个文件正是新分支try001的head指向。
查看master和try001文件,你会发现它们都指向同一个commit。
现你要实现你的想法了,在分支try001上你做了修改,添加了C2.txt并做了提交。
查看./refs/heads/try001,它已经指向了新的提交。
查看tree 1579b30里的内容为:
此时你已经完成了你的想法,并且发现其可行,所以要将try001合并到master分支上。
查看master head发现它也指向了最新的提交。
由于创建try001后并没有在master分支上做过修改提交,所以合并的时候master head会Fast-forward到try001分支的head所指向的commit。如果创建try001在master分支上做过修改提交情况就不会是这样了。
因为try001分支已经不需要了,所以我们将其删除。
到这里你有没有发现,我们的branch只不过是指向某个commit的head指向,而这个commit保存了一次完整的快照中捕获提交时版本的状态。通过移动head指向到不同的commit就可以穿越时空。
假设此时,你的老板告诉你线上有一个bug要修。于是,你又创建了一个新的分支issue001,在其上面修复bug。
由于修改了C1.txt的内容,所以会重新生成一个blob bdacd06。并且由于删除了C0.txt。所以此时tree的内容为:
与此同时,项目又有一个紧急的feature要上,于是你又要先回到master分支上完成这个feature。
此时master分支的新提交tree的内容为:
可以看到除了新添加的blob 7697643之外,其它的blob依然指向以前的blob,并没有受到issue001上修改的影响。
添加完feature后,你又回到issue001分支上继续修复bug。
由于在创建issue001分支后,又在master上做了新的提交,所以将issue001 merge到master上时会再生成一个新的merge提交。这个新的commit指向的tree指向了merge后所要指向的多个blob.
查看master head确实已经指向一个新的commit.
查看这个merge commit,它有两个parent,分别指未合并前master head和issue001 head。
至此,我想你已经比较清楚Git背后是如何运做的了,也能理解
每一个提交对象指向一个目录树对象,而这个目录树对象在一张完整的快照中捕获提交时版本的状态
这句话的含意。
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://blog.csdn.net/i_enum/article/details/50557367
有时候我会觉得Git像是一个时光机,它能带我穿越时空回到过去,并且改写历史,但Git的功能又不仅仅如此,它可以让你在多个空间来回穿梭,甚至可以合并两个空间。我一直觉得学习一个新的事物,首先要学习它的基础和思想,这样自己才不至于会迷失。所以在我们能穿越时空之前,有些基础但却非常东西是需要学习的,要不然就会出现像至尊宝一样的状况,拿着月光宝盒念着波若波若蜜但却无法随心所欲穿越时空。
Git维护两个主要的数据结构:对象库(object store)和索引(index)。所有这些版本库数据存放在工作目录下一个名为.git的隐藏子目录中。
首先,让我们看看一个刚init的repo里.git目录中都有些什么吧。
➜ .git git:(master) find . . ./branches ./config ./description ./HEAD ./hooks ./hooks/applypatch-msg.sample ./hooks/commit-msg.sample ./hooks/post-update.sample ./hooks/pre-applypatch.sample ./hooks/pre-commit.sample ./hooks/pre-push.sample ./hooks/pre-rebase.sample ./hooks/prepare-commit-msg.sample ./hooks/update.sample ./info ./info/exclude ./objects ./objects/info ./objects/pack ./refs ./refs/heads ./refs/tags
里面的目录都是用来干吗的我们先不用管,需要先注意的是这个objects目录。我们再添加一个event.txt看看之后这个目录会发生什么变化。
➜ meet-you git:(master) echo "meet xiaoxing 2015.10.5" >> event.txt ➜ meet-you git:(master) ✗ git add event.txt ➜ .git git:(master) find . . ./branches ./config ./description ./HEAD ./hooks ./hooks/applypatch-msg.sample ./hooks/commit-msg.sample ./hooks/post-update.sample ./hooks/pre-applypatch.sample ./hooks/pre-commit.sample ./hooks/pre-push.sample ./hooks/pre-rebase.sample ./hooks/prepare-commit-msg.sample ./hooks/update.sample ./index ./info ./info/exclude ./objects ./objects/e4 ./objects/e4/51e1f97987612782f91943c770272eb031e64c ./objects/info ./objects/pack ./refs ./refs/heads ./refs/tags
我想你已经看到了objects目录下多了些什么。那么我们再把这个event.txt提交后再看看有什么变化。
➜ meet-you git:(master) ✗ git commit -m "add event about first time meet you" ➜ .git git:(master) find . . ./branches ./COMMIT_EDITMSG ./config ./description ./HEAD ./hooks ./hooks/applypatch-msg.sample ./hooks/commit-msg.sample ./hooks/post-update.sample ./hooks/pre-applypatch.sample ./hooks/pre-commit.sample ./hooks/pre-push.sample ./hooks/pre-rebase.sample ./hooks/prepare-commit-msg.sample ./hooks/update.sample ./index ./info ./info/exclude ./logs ./logs/HEAD ./logs/refs ./logs/refs/heads ./logs/refs/heads/master ./objects ./objects/8e ./objects/8e/8c172536c06aad3df1f3f1636e83145cb809f0 ./objects/e4 ./objects/e4/51e1f97987612782f91943c770272eb031e64c ./objects/e4/99e9d89a87cd282f6c6bd0668e118322248d91 ./objects/info ./objects/pack ./refs ./refs/heads ./refs/heads/master ./refs/tags
同样的,在objects目录下又多出了一些东西,那这些新增的文件都是什么呢?
首先看一下master里面现在存的是什么。
➜ .git git:(master) cat ./refs/heads/master e499e9d89a87cd282f6c6bd0668e118322248d91
可以看到里面存了一个Sha1值,可能你已经注意到这个Sha1值就存在于objects目录下。那么打开这个object文件看看里面的存是什么。
➜ .git git:(master) git cat-file -p e499e9d89a87cd282f6c6bd0668e118322248d91 tree 8e8c172536c06aad3df1f3f1636e83145cb809f0 author Yuanlei HONG <ishuiyutian@gmail.com> 1452819471 +0800 committer Yuanlei HONG <ishuiyutian@gmail.com> 1452819471 +0800 add event about first time meet you
可以看到此文件中存放的是我们执行
git commit -m "add event about first time meet you"时的commit信息,而第一行
tree 8e8c172536c06aad3df1f3f1636e83145cb809f0里又有一个Sha1值,它又对应了一个objects目录下的一个文件,我们再打开这个文件看看。
➜ .git git:(master) git cat-file -p 8e8c172536c06aad3df1f3f1636e83145cb809f0 100644 blob e451e1f97987612782f91943c770272eb031e64c event.txt
同样它里面也包含了一个Sha1值,它同样应了一个objects目录下的一个文件,我们再打开这个文件看看。
➜ .git git:(master) git cat-file -p e451e1f97987612782f91943c770272eb031e64c meet xiaoxing 2015.10.5
这次里面的内容是我所添加的文件event.txt里面的内容。至次三个新出新的object文件的内容都已经看完了,那么让我们把它们的关系串联一下,会得到下面的关系图:
对象类型
对象库是Git版本库实现的核心。它包含你的原始数据文件和所有日志消息、作者信息、日期、以及其他用来重建项目任意版本或分支的信息。Git放在对象库里的对象只有4种类型:块(blob)、目录树(tree)、提交(commit)和标签(tag)。就是由这4种对
4000
象构成Git高层数据结构的基础。
块-blob
文件的每一个版本表示为一个块(blob)。一个blob保存一个文件的数据,但不包含任何关于这个文件的元数据,甚至连文件名也没有。目录树-tree
一个目录树对象代表一层目录信息,它记录blob标识符、路径名和在一个目录里所有文件的一些元数据。提交-commit
一个提交对象保存版本库中一次变化的元数据,包括作者、提交者、提交日期和日志消息。每一个提交对象指向一个目录树对象,而这个目录树对象在一张完整的快照中捕获提交时版本的状态。标签-tag
一个标签对象分配一个任意的且人类可读的名字给一个特定对象,通常是一个提交对象。小结
理解了各个对象的含意后,我们可以知道各种对象之间的关系是这样的:其中一个commit会包含一个tree指向,也会包含一个parent指向,首个commit没有parent;一个tree包含一个或多个blob,同样也可能包含零个或多个tree的指向;blob里面保存一个文件的数据,仅仅是数据,不包含其它的内容,不同的文件名,只要内容相同,都指向同一个blob。
例子
假设你现在一个新的repo上工作,你像日常一样一步一步的小步提交代码:➜ git-learning-01 git:(master) echo "C0 content" >> C0.txt ➜ git-learning-01 git:(master) ✗ git add . ➜ git-learning-01 git:(master) ✗ git commit -m "add C0.txt"
此时.git/objects/目录下结构是这样的
./objects ./objects/13 ./objects/13/96bdbb476d2be51656f79c190fd3112fd806b2 ./objects/7a ./objects/7a/d3d7c724dfe332a79447eb62b431f72abfaaf8 ./objects/bb ./objects/bb/5903c118c57a958612d03cd471fa2bb46c0878 ./objects/info ./objects/pack
然后,像向一步一样又提交一个C1.txt:
➜ git-learning-01 git:(master) echo "C1 content" >> C1.txt ➜ git-learning-01 git:(master) ✗ git add . ➜ git-learning-01 git:(master) ✗ git commit -m "add C1.txt"
些时,提交图结构是这样的:
可以看到此时master已经指像了最新的commit ac3761d,而最新的commit的parent指向了上一次的commit,同时commit的tree属性指向了tree 2422ec4,对于tree 2422ec4来说它指向了两个blob。
假设此时你突然有一个想法想实践一下,但是你现在并不想在master分支上做修改,于是你建了一个名为try001的分支,等验证这个想法确实可行后你想再将其合并到master分支上。
➜ git-learning-01 git:(master) git checkout -b try001 Switched to a new branch 'try001'
此时如果查看.git下的目录,你可以看到./refs/heads目录下多了一个try001的文件,这个文件正是新分支try001的head指向。
➜ .git git:(master) find . ... ./refs ./refs/heads ./refs/heads/master ./refs/heads/try001 ...
查看master和try001文件,你会发现它们都指向同一个commit。
➜ .git git:(try001) cat ./refs/heads/try001 ac3761d04610f192fe722dd47fae50703d5106d8 ➜ .git git:(try001) cat ./refs/heads/master ac3761d04610f192fe722dd47fae50703d5106d8
现你要实现你的想法了,在分支try001上你做了修改,添加了C2.txt并做了提交。
➜ git-learning-01 git:(try001) echo "C2 content" >> C2.txt ➜ git-learning-01 git:(try001) ✗ git add . ➜ git-learning-01 git:(try001) ✗ git commit -m "add C2.txt"
查看./refs/heads/try001,它已经指向了新的提交。
➜ .git git:(try001) cat ./refs/heads/try001 87e8d6f76aeb3b8a8c476f90eb49a35641135773
查看tree 1579b30里的内容为:
➜ .git git:(try001) git cat-file -p 1579b300de845352f6f50feb1366376ae98d7079 100644 blob bb5903c118c57a958612d03cd471fa2bb46c0878 C0.txt 100644 blob 731fa3e52a5bf1f7839ad908b373fee4e591f2cd C1.txt 100644 blob fa31961c8e2c96ac6d8814538966aff625d5e37f C2.txt
此时你已经完成了你的想法,并且发现其可行,所以要将try001合并到master分支上。
➜ git-learning-01 git:(try001) git checkout master Switched to branch 'master' ➜ git-learning-01 git:(master) git merge try001 Updating ac3761d..87e8d6f Fast-forward C2.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 C2.txt
查看master head发现它也指向了最新的提交。
➜ .git git:(master) cat ./refs/heads/master 87e8d6f76aeb3b8a8c476f90eb49a35641135773
由于创建try001后并没有在master分支上做过修改提交,所以合并的时候master head会Fast-forward到try001分支的head所指向的commit。如果创建try001在master分支上做过修改提交情况就不会是这样了。
因为try001分支已经不需要了,所以我们将其删除。
➜ git-learning-01 git:(master) git branch -d try001 Deleted branch try001 (was 87e8d6f).
到这里你有没有发现,我们的branch只不过是指向某个commit的head指向,而这个commit保存了一次完整的快照中捕获提交时版本的状态。通过移动head指向到不同的commit就可以穿越时空。
假设此时,你的老板告诉你线上有一个bug要修。于是,你又创建了一个新的分支issue001,在其上面修复bug。
➜ git-learning-01 git:(master) git checkout -b issue001 Switched to a new branch 'issue001' ➜ git-learning-01 git:(issue001) ✗ git rm C0.txt rm 'C0.txt' ➜ git-learning-01 git:(issue001) ✗ echo "fixing C1 content" >> C1.txt ➜ git-learning-01 git:(issue001) ✗ git commit -m "fix bug step01"
由于修改了C1.txt的内容,所以会重新生成一个blob bdacd06。并且由于删除了C0.txt。所以此时tree的内容为:
➜ .git git:(master) git cat-file -p b5ee4eb316462fcf6b650d951eb741ec88537a04 100644 blob bdacd0631137b01d3adf2e6c68c375ef62809488 C1.txt 100644 blob fa31961c8e2c96ac6d8814538966aff625d5e37f C2.txt
与此同时,项目又有一个紧急的feature要上,于是你又要先回到master分支上完成这个feature。
➜ git-learning-01 git:(issue001) git checkout master Switched to branch 'master' ➜ git-learning-01 git:(master) echo "C3 content" >> C3.txt ➜ git-learning-01 git:(master) ✗ git add . ➜ git-learning-01 git:(master) ✗ git commit -m "add C3.txt"
此时master分支的新提交tree的内容为:
➜ .git git:(master) git cat-file -p 4dbfe384bfc8dfa1fef43a3554f622de125d4dec 100644 blob bb5903c118c57a958612d03cd471fa2bb46c0878 C0.txt 100644 blob 731fa3e52a5bf1f7839ad908b373fee4e591f2cd C1.txt 100644 blob fa31961c8e2c96ac6d8814538966aff625d5e37f C2.txt 100644 blob 7697643fb394431e0180fd9e99cffc105baa0f6e C3.txt
可以看到除了新添加的blob 7697643之外,其它的blob依然指向以前的blob,并没有受到issue001上修改的影响。
添加完feature后,你又回到issue001分支上继续修复bug。
➜ git-learning-01 git:(issue001) echo "C4 content" >> C4.txt ➜ git-learning-01 git:(issue001) ✗ git add . ➜ git-learning-01 git:(issue001) ✗ git commit -m "fix bug step02"
➜ git-learning-01 git:(master) git merge issue001 Removing C0.txt Merge made by the 'recursive' strategy. C0.txt | 1 - C1.txt | 1 + C4.txt | 1 + 3 9c25 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 C0.txt create mode 100644 C4.txt
由于在创建issue001分支后,又在master上做了新的提交,所以将issue001 merge到master上时会再生成一个新的merge提交。这个新的commit指向的tree指向了merge后所要指向的多个blob.
查看master head确实已经指向一个新的commit.
➜ .git git:(master) cat ./refs/heads/master f0d77a5f61c3bc031c15e2b873f69644e3a52ba6
查看这个merge commit,它有两个parent,分别指未合并前master head和issue001 head。
➜ .git git:(master) git cat-file -p f0d77a5f61c3bc031c15e2b873f69644e3a52ba6 tree efb479df54c35870e6f39d0bb13fd9f6b00b1927 parent 8fc237c959a3192316bd902878a942e1241f37e7 parent a8f46f4bffd4700bc5bde3f4407511f0c074c49a author Yuanlei HONG <ishuiyutian@gmail.com> 1453306820 +0800 committer Yuanlei HONG <ishuiyutian@gmail.com> 1453306820 +0800 Merge branch 'issue001'
至此,我想你已经比较清楚Git背后是如何运做的了,也能理解
每一个提交对象指向一个目录树对象,而这个目录树对象在一张完整的快照中捕获提交时版本的状态
这句话的含意。
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://blog.csdn.net/i_enum/article/details/50557367
相关文章推荐
- RPC failed; result=22, HTTP code = 411
- git更新已經刪除的文件
- 提取Git每次提交后Commit的文件
- GIT迁移服务器
- 分布式版本管理git入门指南使用资料汇总及文章推荐
- git终极指南:在实际开发中的应用
- Git远程操作详解
- 25个 Git 进阶技巧(翻译)
- 详解版本控制利器Git,SVN的异同以及适用范围
- Ruby实现的删除已经合并的git分支脚本分享
- 在 Shell 提示符中显示 Git 分支名称的方法
- Git使用基础篇(一些常用命令和原理)
- git fork同步是什么意思?
- Git使用小坑 Out of memory错误的解决方法
- Python的高级Git库 Gittle
- 使用GIT进行源码管理――GUI客户端小结
- 使用git代替FTP部署代码到服务器的例子
- linux系统安装git及git常用命令
- 分享下自己总结的Git常用命令
- Git 常用命令速查表(图文+表格)