您的位置:首页 > 移动开发 > Objective-C

Git背后的object

2016-01-21 20:05 471 查看
Git是一个版本库,它用来维护与管理项目的修订版本和历史信息。

有时候我会觉得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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  git