一、Git基本概念
-
1.1、什么是版本控制
版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。
- 实现跨区域多人协同开发
- 追踪和记载一个或者多个文件的历史记录
- 组织和保护你的源代码和文档
- 统计工作量
- 并行开发、提高开发效率
- 跟踪记录整个软件的开发过程
- 减轻开发人员的负担,节省时间,同时降低人为错误
就是多人协同合作开发时的一种文件管理技术。
-
1.2、版本控制的分类
版本控制可分为以下三类:
本地版本控制(RCS):适合个人使用,对文件每次的更新都可以生成一个版本的快照。
集中版本控制(SVN):所有的版本保存在服务器上,用户需要手动从服务器下载或同步代码,在修改后提交服务器。
分布式版本控制(Git):所有版本信息及代码文件全部同步到本地的用户中,只要有一台用户的数据,就可以还原服务器的数据,实际上服务器也可以看成是一个本地用户,只不过它不进行本地修改等操作,作为本地用户之间的一个中转站。
SVN及GIt是市面上较为常用的版本控制,其主要的区别在于集中式与分布式
SVN版本库在服务器中,协同操作eg:A、B同时修改C文件,最先上传的修改成功,后上传的会提示文件冲突,A修改C文件、B修改D文件,上传服务器后,本地更新即可互相查看,每次上传的是跟服务器文件的diff。
Git版本库在本地中,可以实现本地相互推送完成版本协同,每次上传的是版本快照(即修改的独立文件)。
二、GIt框架及流程
-
2.1、Git框架
Git分为四个区域:
1、工作目录 (Work) 2、暂存区(Stage/Index)
3、本地仓库 (Repository) 4、远程仓库(Remote)
-
2.2、Git基本流程
Git中工作流程为在Work中添加、修改文件→将文件 git add 到Git库中→将git文件git commit提交仓库
故而可将文件状态分为三类:已修改(Modified)、已暂存(Staged)、已提交(Committed)
三、Git基本原理
-
3.1、Git存储原理
Git中一共存在三种数据对象
Blob ------------- 仓库中的文件
Tree --------------仓库中的文件夹
Commit ----------每个版本的对象
Git实质为一个存放不同对象的hash table,hash table的key值为对象的hash值,这样即可保证不同对象之间不会相互覆盖。
以下通过实验验证Git存储的原理
- 1、建立一个新的 Git repo
|
- 2、建立新文件并添加到Git
|
现在去查看git中hash table的对象
|
可以看到在执行git add后其实git中就已经存储了文件的信息,而不是仅仅只加到了暂存区Index,我们查看16f4c48319f2bb70f5289fc1ea325dabc9d14729 对象
|
index文件确实已经作为Blob对象存储到了Git hash table 中。
-
3、建立一个新文件夹里的新文件
|
再查看一下Git的hash table
|
增加了一个文件增加了一个Blob对象,符合预期
-
4、上传本地仓库
|
当git commit 后再查看hash table可以发现在预期的两个Blob对象外还出现了两个Tree 和一个 commit
|
查看一下b0c7尾号的Tree,可以发现里面包含了一个Tree对象和index文件,再查看a9d9的Tree
|
里面存放的inside文件,对应我们测试目录不难发现,a9d9的Tree就是dir文件夹,b0c7的Tree即是我们根目录
最后再看看commit对象
|
可以看到commit对象包含了整个版本的根目录对象Tree的指针、commitrer的个人信息以及注释。
以上,我们可以总结出其三者间的联系
|
-
3.2、Git的存储方式
前文描述到Git并不是像SVN一样存储不同版本的差异,而是把同一个文件的每个版本当作独立的文件存储,将快照存放于Index区域中。
当我们继续验证,修改index文件
|
查看修改之后的hash table
|
相对于修改之前,多了一个blob、一个tree以及一个commit对象,进去index文件
|
即证明Gite存储了同一个文件的每个版本文件,查看修改后的tree对象
|
tree对象指向的dir指针与修改前相同,因为dir没有发生改变,而修改的index文件的指针已经变成了新的blob的hash值,成为了一个新的blob对象。
最后查看修改后的commit对象
|
跟修改之前的区别在于:修改后的commit指向了新的tree根目录对象,多了一个指向上一个版本的parent指针,由此可见commit是一个单向链表。
有下图可以概括其存储的方式:
-
3.3、Git分支与标签
到这里我们已经知道:
commit是一个单向链表结构,所以只要我知道最新的commit,我就可以获得以往所有的commit即以往所有的版本,当我们知道commit2在这个单向链表中的位置,就可以通过根目录的tree获取到所有的版本文件,但是我们该如何找到commit2的位置?
本文一开始讲到,Git相当于hash table,而每个hash table 的键值就是每个对象的hash值,比如commit2的键值为82b70486b425018a6f9250f5e50ffdbc3db24359,你可以直接以键值切换到对应的版本中。
但是键值过于冗长,不易于记忆,因此Git增加 branch 概念代替键值记忆位置,其实质上是指向某个commit的指针,存放在./.git/refs/heads里面。
一般来说,一个新建的Git repo有一个默认的master branch,其存储在./.git/refs/heads/master文件内,我们可以cat查看
|
可以看出master就是指向一个commit的指针。
HEAD是一个指向工作目录的commit指针,其存储在./.git/HEAD文件中
HEAD的作用:HEAD指向的那个branch在commit时,branch指针会自动的指向最新的一个
|
HEAD指向的是一个branch,即commit指针
HEAD也可以不指向branch,Git就会警告detached HEAD,这个时候commit 很容易会丢失,不过你可以创建一个分支,就像master一样。
tag相当与静态的branch,其存储在./git/refs/tags中,当HEAD指向它时,它不像branch一样跟随着HEAD移动指向最新的commit,tag只会一直指向标记的那个commit。
四、深入理解Git原理
-
4.1、Index空间
Index又为stage/cache。
在我初始的理解状态中Index为一个缓存区,每次新切换分支时即会清空,使用git add的时候上传到缓存区,并没有加入Git数据库中。
以上理解中存在以下错误:
- Index并不是临时存放新文件的地方,切换分支、上传到仓库会清空数据
- 新文件并不是在git commit的时候才加入git数据库,在git add时就已加入
为了验证以上论点,我们将index.txt修改并git add
|
在第三章我们可知,git add时index文件已经加入到了Git的hash table中,Git数据库中已经加入了index文件
所以执行git add时是更新Index中的blob对象指针数据,如果我们接着执行 git commit,那么 Git 会基于Index空间内部指向的数据来生成一个 tree 对象,最终再生成一个 commit 对象。
而第一点,上传或切换分支时是不会清空Index区域的,这一点可以大家自行实验证明。