软件构造 这门课在实验中经常要用到Git指令,Git的指令也很简单,简单来讲常用的只有pull、commit、push,但是我们并不了解Git的很多原理以及细节,这篇blog会详细介绍这些东西。
本文中使用了git tree指令,在windows使用该指令可以参考文章:Git Bash下安装使用tree(在windows)
本文章主要参考视频https://www.bilibili.com/video/av77252063
Git如何存储信息
Git内部使用二进制的形式存储 ,如果使用cayt指令查看.git文件中的内容,看到的均是乱码。
新建.txt文件并且将其加入到暂存区中
git init
echo '111' > a.txt
echo '222' > b.txt
git add *.txt
使用tree .git/objects
指令查看
使用cat指令查看内容,输出如下,因为git对数据进行了多次压缩,所以如果直接使用cat查看的话得到的数据均是乱码
xK▒▒OR0a044▒K▒
可以使用以下命令
git cat-file -t file_name //输出文件类型
git cat-file -p file_name //输出具体内容
上面中我们可以看到文件夹Objects和blob,Git Object为储存数据的最小单元,bolb object为储存文件的具体内容和。
在add的两个文件中,有一个文件的文件名为c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c,这是通过哈希算法得到的文件名,也就似乎一个Key值。Git内部实际上就是哈希表的结构,哈希值就是文件名,Value为blob。
接下来我们使用git commit指令,之后使用tree指令查看,可以看到多出来两个文件
以文件4caa为例,使用git cat-file指令查看多出来的文件的类型和内容
可以看到的多出来的文件的类型为tree,储存的内容是一个目录结构,从左往右依次为文件权限、文件类型、文件哈希值、文件名。使用commit指令之后Git对我们得到文件打了一次快照,储存在了tree object中。tree object中的哈希值可以帮助我们定位到本地库中的blob object。
对文件5a74同样使用git cat-file指令
其中存储的是作者、提交时间以及提交内容。
我们最后便可以得到如下的内容
分支信息存储在.git/HEAD中。可以用cat .git/HEAD指令查看目前的分支在哪个位置,之后使用cat指令查看分支内容,可以看到这是一个哈希值,他指向的就是我们刚刚的commit
HEAD、分支、普通的tag可以简单理解为一个指针,指向对应commit的哈希值,可以用下图表示
总结
Git object一旦创建之后就不可变更,但是最后一项可变更。
为何将文件权限和文件名存储在tree object而不是blob object?
如果将权限和文件名存储在blob object中,就算我们没有改变文件内容,只是修改了文件名,Git都会新建一个blob object文件,如果将这些信息存储在tree object,则只需要新建tree object即可。
Git的三个分区以及变更历史的形成
三个分区
- 工作区
- 暂存区
- 仓库
刚刚的任务执行完之后,git三个分区的结构如下(索引也就是我们的暂存区)
使用git add可以将blob添加到暂存区,也就是创建blob文件,使用commit可以将文件提交到本地仓库,也就是创建tree node 和 commit。
如果我们修改a.txt中的内容(echo ‘333’ > a.txt),暂存区和仓库不会发生变化。之后使用git add a.txt,git仓库中会新建一个blob object,内容就是a.txt压缩之后的内容,之后将索引中原本指向a.txt的指针会指向新的a.txt
当我们执行commit指令之后,git的动作可以分为三步
- 新产生一个tree object,指向原来的b.txt和新的a.txt
- 产生一个新的commit,记录这次提交的信息,并且产生指向上一个commit的parent指针
- master的指针移动到新的commit
从以上三步我们不难发现,git每次commit之后,会生成一个全新的文件快照(tree object),而不是一个变更的部分,虽然耗费的空间比较多,但是时间复杂度低,以空间换时间。当文件很大的时候,git会自动压缩相似快照,无需我们手动操作
Git指令怎么保证历史记录不被篡改?
Git使用哈希表的结构,修改某一个节点上的文件,该节点的父节点以及不断往上的节点的哈希值都需要被修改,当其他人使用pull指令或者push指令的时候就会发现问题