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

f8669d870708b3b80695a270db4a73c8.png

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文件

140e38e12eb2a6a61a67eca3884c6a1c.png

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

$ git add main.js

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

86cb9e569d91c7221a2af2a27814e09a.png

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

d9359d8b63999035d0c2435a11ab8bd6.png

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

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

24d39aaf48bdfb5bf11b951fb6e7b0c0.png

e507ce8378cc9b75d51c841b3b2ad272.png

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

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

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

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

b4548b00376de51884f40735ecc515fc.png

03a78b05fba367fc877fe314f7d8e80a.png

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

f7d145cc896900e6aa44bc8900df7c7c.png

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

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

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

684ec147003e4c1f5d1c473474796d28.png

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

$ git reset main.js

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

三.提交对象

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

73840ddab7097190ab78356dac8972cf.png

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

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

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

$ git commit -m 'version 0.0.1'

ac5ee7c148a1c6283911cc03b7cd0f08.png

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

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

$ git log
$ git cat-file -p 958f9643b3d402c681ea94ba478dccd47d917b92

7da1ef80efc9af5d45d02dd94d4499fe.png

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

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

b9da2237a7d33410d81f3c6ff02e7cb6.png

这个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 然后找到提交对象和树对象

929c876c3695a1dd2d6fc6e5834a2eb4.png

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

5a517567f18dc2e02827e6b8fde2a979.png

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

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

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

3653c9fc156bc9b28eab7fa8ed0ad8b3.png

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

合并大致过程如下

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

1a203764714d66a061a8a692da0a5af0.png

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

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

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

7b977f27246cadc8830af1502b9ac324.png

四.git引用及分支

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

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

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

e77d99f7bb27a451300b3522ea34e6c0.png

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

$ git branch  dev

cc76f18c2b427f6f9d947c7b361549c5.png

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

047b48eef1f8e953d5060eb67bc4d31c.png

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

答:是通过HEAD,所以HEAD 指向分支,然后分支再指向提交

eaa0531b1d24385fd67ecefbba9f954b.png

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

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

参考链接:

Git - Git 对象

06004f2853eea271098f34a4288b43f6.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值