天天看点

git 修改分支名字_探究git原理

git 修改分支名字_探究git原理

git是最近几年非常热门的版本控制系统,再学习git第一课的时候,都会先讲一下git和其他版本控制系统的区别,除了git是分布式的以外,还有一个重要的原因是git每次提交版本是

直接记录快照,而非差异比较。

下面就快照为切点,去探究下git内部原理。

一.什么是文件快照(snapshot)?

In computer systems, a snapshot is the state of a system at a particular point in time.

边实践边理解,首先先创建一个新的文件夹并初始化

$ mkdir git_test
$ cd git_test
$ git init 
$ touch main.js 
$ echo 'let version = 0.0.1' > main.js //向main.js中插入一行代码
           

这时打开git_test文件夹可以看到有一个.git的隐藏文件夹,和main.js文件

git 修改分支名字_探究git原理

打开.git ->objects 发现其中有两个文件夹info和pack,并且都为空,除了这两个没有任何文件,这时我们执行一个添加暂存的命令。

$ git add main.js
           

再次打开.git ->发现多了一个40文件夹objects

git 修改分支名字_探究git原理

40文件夹里面有个f16c21571d281acedb5d5de273768763061ca1文件,如下

git 修改分支名字_探究git原理

这时一个文件快照生成了。

git在生成快照的时候做了几件事?
  1. 它把main.js的文件内容加上部分头信息(稍后说明)用SHA-1 校验运算而得出校验和,长度为40个字符。例如40f16c21571d281acedb5d5de273768763061ca1。
  2. 把这个校验和分成两部分,前2个字符作为文件夹名创建文件夹,后面38个字符作为未来生成文件的名字。
  3. 把main.js的文件内容加上头部信息进行zlib 压缩,以校验后面38个字符为名字生成文件,放进文件夹中。
头部消息格式: 类型字符串比如‘blob 加空格,随后是数据内容的长度,最后是一个空字节(null byte):

大家有没有想过为什么要在把SHA-1 校验和分开并且要放一个文件夹呢?我猜想可能是为了增加检索效率吧^_^

为什么要有SHA-1 校验作为文件名呢?

个人理解这其中可能有几个方面的原因

1.文件在传输过程中的确保完整性,因为SHA-1跟md5一样都是不可逆加密算法,相同的内容加密处理得到的结果都是一样的,所以只要再次对比内容校验和与文件名是否匹配,就知道文件是否被篡改。

2.因为git是分布式版本控制系统,跟svn那中集中式有很大差别,git的提交是在本地生成的,所以如果像svn那种递增的版本号,会冲突。这样做的话除非两个文件完全一样,否则不可能重名,在算法层面避免了重名的问题。

二.使用git底层命令查看快照内容

这时我们在采用底层命令去看看这个文件到底有什么?

git cat-file 可以从git那里取回数据

  • 参数-p选项可指示该命令自动判断内容的类型,并为我们显示格式友好的内容:
  • 参数-t 可以显示文件类型

参数后面加上 快照的唯一标识(也就是文件夹名加上文件名)

$ git cat-file -p 40f16c21571d281acedb5d5de273768763061ca1
$ git cat-file -t 40f16c21571d281acedb5d5de273768763061ca1
           
git 修改分支名字_探究git原理
git 修改分支名字_探究git原理

显示出来了刚才保存的内容,类型为blob

这时候我们做一个小实验,如果我们再次修改这个文件,然后再次添加会怎样

$ echo 'let version = 0.0.1 m' > main.js //向main.js中加一个空格和字母m
$ git add main.js
           

这时我们再去看.git -> objects文件夹,多了一个52文件夹

git 修改分支名字_探究git原理
git 修改分支名字_探究git原理

然后使用上面的命令去查看两个快照对象

git 修改分支名字_探究git原理

发现生成的新的快照并没有记录添加的差异,而是把文件内容复制了一份生成了一个新的快照。

结论:

这里就可以明确的看出,git直接记录一个一个的快照,而非文件差异。

然后查看两个文件大小,发现后面生成的文件多了2个字节

git 修改分支名字_探究git原理

如果我们把暂存区撤回到未暂存的时候对象会是怎样的变化呢?

$ git reset main.js
           

我试了下,发现撤回暂存并没有删除本地的快照对象,而再次添加的时候并没有生成新的文件,因为没有改东西,所以得出的校验和没有变。所以也就没有生成新的文件。

三.提交对象

细心的同学可能会发现一个问题,就是这个文件快照没有保存文件名字,而且如果要提交的话,提交的时间和作者的名字以及提交的说明在哪呢?这个问题可以用提交对象来解决,在说提交对象之前先要说下树对象,以及书对象和提交对象以及blob对象之间的关系

git 修改分支名字_探究git原理

灰色的方块代表提交对象,蓝绿色的代表树对象,黄色代表blob对象

  • 提交信息tree对象的指针指向树对象,还有作者和提交者的名字
  • 树对象有当前git管理内的所有最近文件快照的指针
  • blob对象是各种文件快照

下面我们就做个实验,在实践中理解

$ git commit -m 'version 0.0.1'
           
git 修改分支名字_探究git原理

这时.git -> objects 文件中多了两个文件,这里面一个是提交文件,一个是树文件

输出log,找到提交对象标识,然后查看该文件

$ git log
$ git cat-file -p 958f9643b3d402c681ea94ba478dccd47d917b92
           
git 修改分支名字_探究git原理

这里面有四个值,第一是tree对象标识,第二是作者的名字,第三是提交者的名字,第四是提交的注释。

然后我们重点看下tree对象里面的内容

git 修改分支名字_探究git原理

这个tree对象中显示的信息git跟踪文件的列表因为只有一个文件,所有只显示一条

tree对象中都有的内容都代表什么呢?
  • 这里的100644表明这是一个普通文件,其他选择包括:

    100755

    ,表示一个可执行文件;

    120000

    ,表示一个符号链接。
  • blob代表对应文件的类型,也可以是tree或者其他
  • e52fc....代表对象标识
  • main.js代表对应文件名称(解决了上文说的问题)
如果有不同的文件夹层级那么git是怎么处理的呢?

我们添加一个文件夹,里面放一个test.js文件,然后提交

$ mkdir lib
$ cd lib/
$ touch test.js
$ git add .
$ git commit -m 'add lib'
           

然后我们再次查看log 然后找到提交对象和树对象

git 修改分支名字_探究git原理

发现如果是文件夹的话它会创建一个新的树对象,这也让我想到为什么这个对象叫树对象的原因了。

git 修改分支名字_探究git原理

这个过程中,我没有修改main.js文件,但是这个文件还是添加到第二次提交的tree对象中,只不过虽然产生了新的提交对象和tree对象,但是对于未修改的文件,还是指向上个版本的文件快照。

既然每个tree文件都拥有一个完整的文件指向,那么当前提交为什么还要指向父提交?

我个人的理解是这样,首先在看log的时候,会按照提交的指针顺便排序,而且最重要的是在合并分支的时候,可以根据指针的父提交找到两个分支的共同祖先然后进行合并。

git 修改分支名字_探究git原理

上面的master分支和iss53问分支合并,这时他会根据父指针一层一层往上找,他们共同的父指针,以c2为基础,比较 c4,c5差异进行合并。

合并大致过程如下

如果c4的a文件相对于c2被修改,但是c5的文件相对于c2没有修改,那么就直接合并c4修改内容,反之c5修改c4未修改就直接合并c5内容,但是如果相对于c2来说,c5,c4都有修改那么就产生冲突,等冲突解决之后手动提交,如下图示例c6。

git 修改分支名字_探究git原理

因为是在master分支上合并的,所以master分支指向了最新合并的c6提交,这时c6提交产生了两个父提交

为什么c6提交有两个父提交呢?

下面纯属于个人理解,举个例子,如果这时iss53分支,没有合并c6而是又提交了一次,这时如果master再想跟iss53合并就不是以c2为基础合并了,而是他们共同的父元素c5。如果没有c6对c5的指向就又回到以c2为基础合并了

git 修改分支名字_探究git原理

四.git引用及分支

git是怎么知道我当前最近的提交是那个呢?

这部分的信息记录在.git -> refs ->heads 文件夹中

这里面只有一个master文件,代表master分支

git 修改分支名字_探究git原理

如果我们再添加一个dev分支的话,这里面就会多一个dev分支

$ git branch  dev
           
git 修改分支名字_探究git原理

打开文件会发现一个最新提交的指针

git 修改分支名字_探究git原理

那么git是怎么知道我当前所在的分支呢?

答:是通过HEAD,所以HEAD 指向分支,然后分支再指向提交
git 修改分支名字_探究git原理

如果下面有时间我会再次探究下远程的引用以及传输协议^_^

如果你觉得这篇文章对你有帮助,请点赞和关注,非常感谢 。如果有疑问,或者说错的地方,欢迎在评论区留言批评指正~

参考链接:

Git - Git 对象

git 修改分支名字_探究git原理