转载

Git的分治之道

Git的分治之道

1、什么是git

git的官方定义:Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals.

可以看出,git是一个具有丰富命令集的版本控制系统,它的特点就是快速、可扩展以及分布式

而git实际上是一个简单内容记录工具(the stupid content tracker)。从根本上来讲,git是一套内容寻址 (content-addressable) 文件系统,在此之上提供了一个版本控制系统的用户界面。

1.1 an unusually rich command set

在当前版本里,git一共有158个命令,但是常用命令只有10多个,其他命令可以视为底层命令。由于git最开始被设计的时候用来作为the stupid content tracker,只是现在被最广泛地用来作为revision control system,但是实际上git也可以被用来做其它事情。因此,git不仅一开始提供了功能齐全的底层命令,而且还提供了许多友好的高层命令。

git命令分为两种:

  • Procelain(高层命令)

user-friendly commands: init, add, commit, push, checkout, branch, merge, etc.

  • Plmbing(底层命令)

bunch of verbs that do low-level work: hash-object, update-index, write-tree, etc.

1.2 revision control system

版本控制系统,学名:software change and configuration management system(SCM)。它需要具有的特点如下:

  • 记录和控制软件的变化点和版本号

  • 能清楚知道什么东西被改了,被谁改了

  • 在多人完成的大项目中,能更加容易的合作和管理文件

  • 最重要的是,要有回滚功能

在实际生活中,版本控制就被经常用到,例如写论文时的版本控制。但是,这只是简单的本地版本控制。在多人的项目合作中,这就显得力不从心了。因为使用这种方法时,首先需要大量的拷贝工作,以保证能够回滚嘛;其次当多人更改同一项目出现写写冲突情况时,虽然Linux提供diff和patch命令,但是解决起来肯定极其痛苦。

因此,版本控制系统,核心就是让在不同系统上的开发者协同工作,需要解决的主要问题就是防止各种人一块写最后写乱套的情况。

2、git有何不同

2.1 集中式版本控制系统

首先,谈谈传统的集中式版本控制系统。

Git的分治之道

集中式版本控制系统中,版本库是集中存放在中央服务器(server),每个开发者都作为客户端(client)。协同工作的开发者都通过客户端连到中央服务器,取出(checkout)最新的文件或者提交(commit)更新。它的主要特点如下:

  • 每次checkout拿到的都是当前版本库最新的一个快照状态(snapshot)。它并不知道版本库之前的历史状态信息。

  • 每次commit都会更新中央版本库。

  • 为了保证不丢失数据,在每次commit时都需要re-sync。

而集中式版本控制系统的缺点,则有如下几点:

1. 对于每次commit操作,都需要re-sync;

对于集中式版本控制系统,在多人协作中,可以认为如果commit越早,麻  烦也就越少。

从这个角度上看,可以认为git做了两步commit操作,第一次为commit,第二次为pull,只有pull才需要re-sync。

2. 单点故障

在没有commit之前所有文件都在于client端的本地文件,如果此时不小心删除掉了一些文件,除非重新访问server进行回滚操作,否则就真的删除掉

当server没有备份并且宕掉了,就算能恢复当前版本的数据,但是之前的历史数据信息是恢复不了的。

3. 每次工作的时候,需要联网;

4. 速度慢;

大部分时间都是需要client和server进行沟通协作的。

2.2 分布式版本控制系统

我们再来看看git,它是一个分布式版本控制系统。

Git的分治之道

与传统的集中式版本控制系统相比,git最大的不同在于,它是分布式版本控制系统。分布式意味着,不止服务器保存有version database,每个客户端也保存有version database。git的主要特点如下:

  • 每次clone都真正的拷贝所有的数据。

  • 在初始clone之后,大多数的操作都是在本地进行的。

而git版本控制系统的主要优点如下三点:

1. Fast:

除了第一次clone的时候慢,之后的操作都比较快。因为大多数操作都是在本地进行的,不需要联网。

2. Scalable:

你可以对本地的version database做任何你想做的事情,他并不会对其他人 造成任何影响。

3. Distributed:

在client端,肯定不法彻底避免单点故障,但是可以减少这样可能性。因为本地保存有version database,所以当本地不小心丢失文件时,往往可以本地出错本地解决。

在server坏掉的时候,可以根据client里面的version database进行恢复,甚至可以把client当成一个新的server。

3、git如何存储内部数据

从上一小节,我们知道git是一个分布式版本控制系统,无论是服务端还是客户端都拥护一份自己的version database。那么本小节,首先看看version database到底是什么。然后,分别深入看看git init、git add、git commit这个三个常用命令具体的工作过程。

version database

git的version database到底是什么?

1. 本质上,是一套内容寻址文件系统(Content Addressable Filesystem)。

2. 实现上,是一个简单的KV数据库

3. Key: sha-1 hash(everything is hashed)

  • 20 bytes, 40 hex, 160 bit, 2.9e48 distinct keys

  • How would  git handle a SHA-1 collision on a  blob?

4.Value: binary files

  • Commits : actual git commits

  • Trees : directories(structure of file system)

  • Blobs : contents of files/data

git以一种类似UNIX文件系统但更简单的key-value方式来存储内容。key是对文件内容的哈希值。value则包含有commit object、tree object以及blob object三种对象。

所有内容以tree或blob对象存储,其中tree对象对应于UNIX中的目录,blob对象则大致对应于inodes或文件内容。commit对象则可以看作是对当前版本的一个快照。

一个单独的tree对象包含一条或多条tree记录,每一条记录含有一个指向blob或子tree对象的SHA-1指针,并附有该对象的权限模式、类型和文件名信息。

接下来一步一步,看来git flow里到底发生了什么:

3.1 git init命令

git init主要是创建了四个文件夹:.git、.git/refs/heads、.git/refs/tags以及.git/objects,和一个文件.git/HEAD,并把初始化./git/HEAD里面的内容为指向master branch的HEAD。

那么如何用普通的Linux命令实现git init呢?答案其实很简单:

mkdir .git mkdir -p .git/refs/heads mkdir -p .git/refs/tags mkdir -p .git/objects touch .git/HEAD echo "ref:refs/heads/master" > .git/HEAD

以上便是git init, without git init的具体实现做法。其中,objects目录存储所有数据内容,refs目录存储指向数据(分支)的提交对象的指针,HEAD文件指向当前分支。

3.2 git add命令

git add做了两件事情:

第一,把文件写成blob object作为value,并把文件内容的hash值作为key;

第二,更新了index文件,把文件放入了暂存区(staging area)。

echo "Hello World" > hello.txt git add .

上面的操作可以用下面底层语句实现:

echo "Hello World" | git hash-object -w --stdin git update-index --add --cacheinfo 100644 557db03de997c86a4028e1ebd3a1ceb225be238 hello.txt git checkout -- hello.txt

第一个命令中,参数-w指示hash-object命令存储(数据)对象,若不指定这个参数该命令仅仅返回键值。该命令输出长度为40个字符的SHA-1哈希值校验和,并创建以它的前2个字符为名称的子目录,剩下38个字符作为文件命名(保存至子目录下)。

第二个命令,update-index为一个单独文件创建一个index。更新完staging area后,由于仅仅Index里有,但是本地仍没有,如果git status,所以会显示为deleted。此时,使用第三条命令可以把repository进行checkout出来。

3.3 git commit命令

我们进行第一次git commit操作。

git commit -m "First Commit"

此时会多出两个文件。

第一个是commit object,它实际上是对于跟踪项目内容的一次快照,里面的格式内容有:指明了该时间点项目快照的顶层树对象、作者/提交者信息以及提交注释信息;

第二个则是那个被指向tree object,它实际上一个文件根目录,里面指向一个blob object,而这个blob object便是之前git add时的内容。

Git的分治之道

虽然执行git log命令可以查看完整的历史信息并以此找到文件,那么问题来了,系统又是如何找到commit log的呢? 答案是:refs!

在.git/HEAD里存的是一个ref,ref是一个file,这个file里面又存了一个commit的hash值,从而便可以找到了当前的base版本。

Git的分治之道

我们尝试进行第二次git commit操作。 首先,git add一个简单的脚本文件hello.sh。

/*************hello.sh的具体内容如下:

!/bin/sh echo "Hello World" **************/ git add hello.sh

此时,git add只会增加一个blob object,而不会增加多一个tree object。也就是说,在add的时候并不写目录,只是更新了暂存区,在index里会保存目录信息。因为在git commit操作前,可以进行多次git add操作。

git commit -m "Second Commit"

此时,会多出三个文件。第一个是commit object,第二个是根目录的tree object,第三个是文件目录的tree object。

通过下图,可以发现commit object中还带有个parent指针,它指向上一次的commit object。最重要的是,它还更新自身的refs/heads/master信息;

Git的分治之道

通过命令git ls-files --stage查看staging area信息。你还可以发现,暂存区里只存储blob object,并不存储tree object和commit object。

4、branch/merge在git内部是如何工作

相对于git init、git add、git commit这三个命令,git branch和git merge则显得十分的小巧机智。结合下列几幅图,能更轻松的理解这两个命令。

4.1 branch操作

在git里,branch操作,实际上仅仅增添一个41-bytes的引用文件(在refs/heads/目录底下)。可以看出,这样的操作是极其的简单廉价的,示意图如下:

Git的分治之道

同理,在git里,checkout操作,实际上也仅仅是更改了引用文件的指针而已。

Git的分治之道

4.2 merge操作

最后,我们来分析一下,git里的merge操作。它的操作流程和示意图如下:

1. 计算出当前branch和公共branch的diff;

这步操作的速度很快。因为只需要去比较文件的hash值是否匹配即可。

2. 应用该diff去订正本地文件;

这步操作并不会覆盖分支里之前的版本数据。

3. 解决所有的冲突;

Git的分治之道

当merge操作完成后,示意图如下:

Git的分治之道

首先,可以发现,这是会新建一个mater处的commit object,而它有两个parent指针,分别指向之前两个branch的commit object。

其次,如果不小心做了一个bad merge,而又不想让他人知道时,其实只需要更改下master和feature指针的hash信息即可。

最后,通过branch和merge操作,你会发现在git的世界里,它会尽量不让你做删除的事情。

5、小结

Git is the stupid content tracker.

You can represent renames on top of git - git itself really doesn't care. In many ways you can just see git as a filesystem - it's content-addressable, and it has a notion of versioning, but I really really designed it coming at the problem from the viewpoint of a _filesystem_person (hey, kernels is what I do), and I actually have absolutely _zero_interest in creating a traditional SCM system. -- by Linus Torvalds

我们应该如何看待并理解git呢?本质上,git是一套文件寻址系统

虽然git具有版本控制的功能,但是在Linus最初设计git的时候,完完全全是从一个文件系统的角度进行设计和实现这个产品的。所以说,git本质上仍然是一个文件系统,只是它很适合地被用作版本控制系统。

正文到此结束
Loading...