工作中经常用git,但是不少命令经常使用出现各种各样的问题,也不太理解其中的原理。今天专门总结一下git的原理,理解原理之后想实现什么样的功能直接找相应的命令即可。如有错误和不足,欢迎指正!
一、 工作区
使用git命令之前首先需要明白git的工作区
版本库:.git目录
工作区:除 .git目录的其他目录和文件
如图所示:git版本库里维护了两个目录树,暂存区目录树和分支目录树(在隐藏的 .git文件中)
更详细一些的四大工作区:
- Workspace: 工作区,就是你平时存放项目代码的地方
- Index / Stage: 暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
- Repository: 仓库区(或版本库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
- Remote: 远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换
二、GIT 实现版本管理原理
git会将我们的文件和目录转化成一种叫做”git对象”的东西,然后再对这些”git对象”进行管理,从而实现版本管理的目的,这些git对象存放在git的对象库中。
文件————>块(blob)
目录————>树(tree)
状态————>提交(commit)
blob、tree、commit都是git对象,是三种不同类型的git对象
一个blob就是由一个文件转换而来,blob对象中只会存储文件的数据,而不会存储文件的元数据。
一个tree就是由一个目录转化而来,tree对象中只会存储一层目录的信息,它只存储它的直接文件和直接子目录的信息,但是子目录中的内容它并不会保存。
一个commit就是一个我们所创建的提交,它指向了一个tree,这个tree保存了某一时刻项目根目录中的直接文件信息和直接目录信息,也就是说,这个tree会指向直接文件的blob对象,并且指向直接子目录的tree对象,子目录的tree对象又指向了子目录中直接文件的blob,以及子目录的直接子目录的tree,依此类推。
图解
开始:工作区只有一个根目录,目录下两个文件 f1, f2 现在进行提交操作 git转换成如下对象
现在修改f2的内容为f22 f1不变,并在根目录下创建子目录dir1,并在dir1新建文件df1
三、文件的四种状态
结合上节版本控制原理,可知不同状态之前的改变原因
不同的工作区存在的文件对应了不同的状态,入下图所示
- Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged.
- Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified,如果使用git rm移出版本库, 则成为Untracked文件
- Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用git checkout 则丢弃修改过, 返回到unmodify状态, 这个git checkout即从库中取出文件, 覆盖当前修改
- Staged: 暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存,文件状态为Modified
图解:
四、命令实现过程与原理
分支
了解git的各种命令之前,需要先明白git分支的概念。git分支可以说是git普及如此之广的最重要的理由之一。
分支其实就是记录一系列逻辑操作的一条线,这条线记录了我们操作过程和每次操作的内容。如下图所示,每一个节点都是一次commit 节点连起来就形成了分支。
分支有什么作用呢?
1:精准编辑回退
分支指针和HEAD指针
搞明白git命令的实现过程,就必须先了解指针的概念,很多命令都是用过指针实现的。
指针是什么呢?
指针是版本库中指向分支中某个提交的标识(每个提交都有一个唯一id),记录了我们当前所在的位置。
每个分支都会有一个分支指针,默认指向当前分支的最新提交(注意提交是个tree 有父节点)。
HEAD指针通过指向分支指针指向当前分支的最新提交
(思考一个问题:当我们把控制台或者git bash操作窗口关闭后,再次打开为什么能直接记录到关闭前所在的分支呢?)
命令实现原理和过程
1. 查看分支提交记录
git log
下面我们看一下这个命令做了什么。比如说我们现在在test分支上,那就是从分支指针指向的commit树开始,依次**找父节点**,然后将信息展示出来,这就实现了记录的查询操作
2.查看不同
git diff
这个命令怎么实现的呢?上文中讲到git的实现原理 ,git会将文件转为git对象,回顾一下:
文件————>块(blob)
目录————>树(tree)
状态————>提交(commit)
现在以比较工作区和暂存区的区别为例:
运行git diff 找到当前最新的提交,从暂存区拿到相应的文件(有目录和文件地址和文件内容)然后和工作区的一行行比较
3.撤销修改(版本回退)
git reset commitID
git reset --hard commitId(实现和rollback一样的功能)
“git reset HEAD”命令将最近提交中的内容覆盖到了暂存区
“git reset –hard HEAD”命令将最近提交中的内容覆盖到了暂存区和工作区
撤销操作是怎么实现的呢?这个还是和指针有关。比如从D回退到C,这时候git做了什么呢?其实很简单,只需将指针指向C就好,这时候根据commit树的id去查内容查到的就是C提交时的内容。
4.合并分支
git merge
git rebase
合并分支应该是非常常用的命令了,下面来解析下合并分支的时候git是如何做的。
首先以git merge 为例
根据之前分析的原理,其实可以很清楚的理解git做了怎样的事情。
两个分支指针分别指向最新的提交,现在做图示一操作,将new分支合并到base上,运行命令base分支新生成一个commit 节点分别指向new分支的最新commit和base分支的最新commit,然后base分支的分支指针指向新生成的这个commit就完成了合并。
git rabse的变基操作,其实就是复制基准点后的变更文件(注意是复制!!并不是直接改父节点,git所有的操作都不会更改原有的内容,虽然提交信息看着一样,但是commitId是不同的!),然后将父节点指向合并分支的最新提交,达到变基的目的。
5.其他
其他的命令都可以参考git实现原理去理解实现方式,只有明白了原理,命令理解起来就很简单了。
文档参考:git系列学习