给那些不怕类似“有向无环图”字眼的人简单介绍git

给那些不怕类似“有向无环图”这样字眼的人简单介绍一下git的内部实现。

存储:

简单的说,git的对象存储就是一堆对象的有向无环图,一堆被分成几种类型的对象。这些对象都压缩存储,并且以其自身的SHA-1哈希值作为唯一标示。(顺便说下,这个哈希值不是对象所表示文件内容的SHA-1,而是对象本身在git中的表示。原文:that,incidently,isn’t the SHA-1 of the contents of the file they represent,but of the representation in git).

git-storage.1

blob:最简单的对象,一些字节而已。可能是一个文件,也可以是一个符号链接或者类似的其他东西。指向blob的对象定义了git中的语义(看到后面大家会理解)。

git-storage.2

tree:目录被表示成tree对象。他们指向了包含文件内容的blobs(文件名,访问权限等呗保存在tree对象里),和其他tree对象(代表了子目录)。

 

再一个有向无环图里,如果一个节点指向另外一个节点,那么被指向节点就依赖指向它的那个节点:没有指向节点,被指向节点就不能存在。没有被指向的节点会被当成垃圾,或被git gc回收,或使用git lost-found复原(这点和文件系统把没有文件名指向的inode放到lost+found目录供以后恢复类似)。

git-storage.3

commit:一个commit指向一个tree对象,这个tree对象代表了commit发生当时的文件状态。commit可以指向0..n个其他的commit作为他的父亲节点。如果commit有多个父亲节点,那意味着这个commit是一个merge;没有父亲节点意味着该commit是一个initial commit。有趣的是git允许有多个initial commit,这意味着多个project的merge。commit对象的主体是commit信息(message)。

git-storage.4

refs:References,heads或者branches很像是贴在有向无环图上节点便条。有向无环图只能添加节点,已有的节点也不会变动。这些“便条”却可以随意移动。他们不会存储到历史中,它们只是在不同的仓库中移动。它们又像是书签,表明了“我在这里”。(译者注:这样我想起了进程调度的current)

git commit命令会在有向无环图中添加一个节点。之后会移动“便条”到新加的节点上。

HEAD这个ref有点特殊:它指向其他的ref,是一个指向当前活跃分支的指针。一般的ref都在类似heads/xxx这样的名字空间下。但是一般你可以忽略heads/部分。

git-storage.5

remote refs:remote References可以理解成另外一个一种颜色的便条。和一般ref不同的是他们所在的名字空间。remote refs由远程的服务器控制,可以通过git fetch命令更新。

git-storage.6

tag:tag既是有向无环图中的一个节点,也是一种便条(当然是另外一种颜色)。tag指向一个commit,包含可选的描述信息和GPG签名。通过tag的便条可以快速访问一个tag,如果丢失,可以使用git lost-found命令根据tag的GPG签名进行恢复。

 

历史

好了,有了上面关于git历史版本存储的知识,我们来看看git如何进行类似merge这样的操作,看看git和其他利用分支线性变化,管理历史的工具的区别。

git-history.1

这是一个最简单的仓库。我们使用clone克隆了一个远端的仓库,并且只有一个commit。

git-history.2

我们使用fetch,接受了来自远程仓库的一个commit,但还没有merge。

git-history.3

我们使用git merge remotes/MYSERVER/master命令之后。这里的merge是一个快进(fast-forward)merge(因为我们本地没有新的提交),唯一要做的就是移动“便条”master,相应的改变我们工作目录中的文件。

git-history.4

这里我们在本地进行了一次提交,之后进行了fetch操作。我们得到了一个本地commit一个远端commit。显然这里需要一次merge。

git-history.5

这是git merge remotes/MYSERVER/master之后的结果。因为我们有本地的提交,所以这次的merge不是快进式的。一个新的commit节点e被生成,它有两个父亲节点。

git-history.6

这是一些commit以及一次merge之后两个分支的情况。看这种”缝合”式的merge,可以看到git保存了所有“行为”(commit或者merge)的历史。

git-history.7

这种“缝合”(原文stitching)模式有时读起来比较冗长。如果你还没有发布你的分支,或者确定别人不会在你的分支之上进行开发,那么你可以选择rebase你的分支。这时,merge操作不会被执行,你的commit会被另外一个commit代替。这个commit和你的commit有不同的parent,如上图。你的分支也会被移到新的commit处。

你老的那个commit仍然会在有向无环图中,直到垃圾收集器回收。(Ignore them for now,but just know there’s a way out if you screwed up totally).注意如果你还有一个便条指向那个老的commit,它仍会继续指向这个commit,这会让这个commit永远存活下去。很令人困惑是吧。

不要rebase 那些别人提交过commit的branch。恢复是有可能的。但是恢复的过程可能会极其痛苦。

git-history.8

这是经过垃圾收集之后,并且在rebased的分支上新加了一个commit的情景。

git-history.9

看rebase命令自己还知道如果处理多次commit的情况。(译者注:这里远端fetch之后多了f和g两个commit,使用rebase命令,d2变成d3并且指向了g,h变成h2,指向d3)

这就是针对那些不怕计算机科学的人的git简介,希望有用~

收藏 2 评论

关于作者:bruce-accumulate

积累技术,努力工作,坚持学习,认真生活。(新浪微博:@bruce-accumulate | 个人博客:zhangsong) 个人主页 · 我的文章 · 1

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部