likes
comments
collection
share

Git:从实践到原理,再到实践

作者站长头像
站长
· 阅读数 11

写在前面

在如今的时代,相信没有人还不会用Git了吧。虽然Git只是用两周时间开发出来的,但因其优秀的性能、简洁的操作,已经成为了使用最广泛的版本管理系统。

在与工作的过程中发现,大家对Git的使用等级,大致分为3种。

①,把Git当成svn使用,有分支的概念,但无法熟练使用 ②,可以熟练使用git,可以应付大部分的Git操作,但无法灵活的操作,发生操作失误时不知道怎么办 ③,基本可以解决项目上所有的git问题,熟练使用一些冷门但实用的命令(reflogrebasecherry-pick等)

对于一个成熟的程序员来讲,如果你还是①的等级,说明你需要学一下Git操作了,否则被市场淘汰只是时间问题了。 如果你达到了②的程度,Git就不再限制你的开发效率,但无法承担起核心的责任。 而如果达到③的程度的话就足以独当一面了,可以自己去设计一套项目的开发流程、分支结构。 但是,真正了解Git的运行、存储原理的人其实非常少,而了解这些会帮助我们更加高效优雅的使用Git。 本篇内容并不适合等级①的开发者阅读,建议先学习一下Git的基础知识后再探究其原理。

起因

在一个适(huo)合(gan)学(wan)习(le)的工作日,沉浸于Git操作的我突然想到,以前学习Git时学到,Git的存储形式不同于SVN的Diff存储,而是将文件以SNAPSHOT(快照)的形式存储,以空间换时间的方式提高效率。 而在实际使用过程中,并没有发现项目的Git占用多大空间,这就需要进一步了解一下Git的原理了。

Git的简单讲解

Git的核心功能涉及到两块知识,第一个是Git的三个分区(工作区暂存区仓库区),第二个是Git的快照存储三种形式(BlobTreeCommit)。 对于Git的分区相信大家应该都了解一些,如果不清楚也不要着急,下面会说到。这里为了方便大家理解先说明一下Git的存储形式。

实践展示-存储单元

从这里开始,我会新建一个空文件夹来说明Git的相关存储形式。

git init

在初始化Git后,我们会看到文件夹根目录下创建.git文件夹,而.git\objects就是Git的存储目录 Git:从实践到原理,再到实践 因为现在是个空文件夹,里面没有文件。.git\objects目录下除了Git生成的文件夹,没有其他内容 现在我们新建一个文件a.txt,没有执行git add的话,.git\objects目录下依然没有内容

git add a.txt

执行后,.git\objects目录中生成了94文件夹和其目录下的ebaf900161394059478fd88aec30e59092a1d7文件 Git:从实践到原理,再到实践 代表着a.txt已经正式交给Git来管控。 而94ebaf900161394059478fd88aec30e59092a1d7拼接后的94ebaf900161394059478fd88aec30e59092a1d7为这个文件的hashId,这个文件就是a.txt的快照。Git中称其为Blob(后续会详细说明)。 ps. git cat-file是Git提供的查看快照文件的命令,了解即可,感兴趣可自行搜索。 Git:从实践到原理,再到实践

这时我们提交一下试试

git commit -m "commit A"

.git\objects目录中又新生成两个快照 Git:从实践到原理,再到实践 我们通过git cat-file命令来查看一下 Git:从实践到原理,再到实践 在Commit时,创建的TreeCommit的快照,并且构建了三个快照之间的关系(图中只写了hashId的前两位) Git:从实践到原理,再到实践 总结一下,Git在执行git add时创建相应文件的Blob快照,在执行git commit时,根据暂存区Blob快照创建相应的TreeCommit快照。完成存储。

实践展示-提交履历

上面介绍了Git的Commit的存储依赖关系,接下来我们再新建一些文件提交,增加一些文件结构和提交履历。 废话不多说,直接上图 Git:从实践到原理,再到实践 提交后看一下.git\objects目录,增加了四个快照 Git:从实践到原理,再到实践 为了节省篇幅我就直接上关系图了 Git:从实践到原理,再到实践 根据关系图,我们可以通过commit B的hashId,获取到这个版本的所有文件。 到这里我们就可以了解到Git的存储机制了,如果不太清楚的话,建议停下来思考一下。 ps. 引申一点思考,这也解释了我们在提交代码时,只需要提交文件的原因,文件的目录是由Git通过创建Tree来创建的。 ps. 再记载一下一些小知识,如果两个文件的内容是一样,只是目录不一样。Git只会创建一个Blob对象。这可能是Git用来优化存储空间的一点小技巧。如果感兴趣可以自己试一下。

回头看一下Git的三个分区

读到这里大家应该对Git的存储形式有了大概的理解,我们回头来整理一下Git的文件分区 Git:从实践到原理,再到实践 结合Git的存储形式可以得知,Git并没有实际文件分区,而是通过分布存储文件,实现了分区的概念。 ps. 再稍微扩展一下,在add之后创建的Blob对象,即使将文件移除暂存区,Blob对象仍然存在,如果有该文件的hashId的话,随时可以查看文件内容

Git指针(HEAD、branch、remote等,统称Reference)

如果理解了上面的内容,可能会发现一个问题。上面说Git都是通过hashId来存储、操作,但我们平时都是用branch来操作的。聪明小伙伴已经猜到了,其实这些branchremote只是一些指向某个Commit hashId的指针。 我们打开自己Git项目的.git\refs目录来看一下,这里记录着指向的Commit hashId。 而HEAD稍有不同,HEAD记录的是当前所在的分支,可以看一下.git\HEAD文件。

重新审视一下Git常用命令

现在,以我们掌握的知识,再来重新审视一下我们常用的Git命令,更清晰的认识Git。

git merge

我们知道,Git并不管理每个版本的变更,只管理每个版本的文件。而merge则是将两个分支的文件进行合并,重新提交一个Commit,而这个Commit会有两个parent指向,这也就是我们平时看到的履历 Git:从实践到原理,再到实践

git reset

其实reset应该分开来讲,因为hardmixedsoft的区别,导致命令作用的差别。 但如果理解了其中本质的话,其他相信大家可以自己理解。 简单来说,reset只是移动了当前branch的commit指向。 比如执行git reset --mixed commit2hash的话,本地文件并没有变更,仅仅变更了master分支的指向,如图所示 Git:从实践到原理,再到实践 这里稍微的扩展一下,由于Git不会删除快照文件的特性,我们可以做一些奇怪但有效的操作。

再比如我们如果不小心merge错分支的话,只要在log中找到merge前的commit hashId,执行git reset --hard commitHash就可以恢复到之前的状态。

需要提醒一下,虽然Git不会删除快照文件,但是工作区的修改不在Git管理范围内,所以使用 reset --hard 时,需要慎重

git checkout

checkout的功能同样很多,这里简单介绍一下常用的操作,其他功能大家可以自己思考一下。 执行git checkout branch1时,首先会修改.git\HEAD中的分支指向,然后抽出该分支对应的Commit下指向的文件,替换到我们的目录下。 工作区如果有修改和其快照文件有冲突时,无法抽出。

而执行git checkout file1时,根据当前Commit中的Tree指向,找到该文件对应的Blob Hash,然后替换至本地目录下。

由于篇幅有限(懒),就写这么多吧。如果有兴趣的话,建议自己研究一下感兴趣的命令,会比其他人讲解收获更多。

总结

在学习Git原理时,我不只一次的感叹Git的设计,也希望大家在学习的过程中不仅仅只是会用就行。 如果了解了设计思想,不仅能提高我们使用的效率,还可以为我们打开思路。 举个简单的例子,空间换时间的性能优化方针大家都懂,但像Git在遵从这种方针的同时,在一些细节上不断优化,这样才能配的上Git当前的地位。