大纲
工作区(workspace directory):本机的代码项目,是一种沙箱环境
暂存区(stage index):工作区在程序员写程序的过程中会发生无数次改动,git不可能记录每一次的改动,这些改动的过程在暂存区负责记录,暂存区会将最终的状态随着程序员的提交而提交到版本库
版本库(history):保存由程序员从暂存区提交上来的程序
工作区 => 暂存区操作指令:git add files 其实做了两件事:
①将本地文件的时间戳、长度,当前文档对象的id等信息保存到一个树形目录index中去,即平时说的暂存区
②将本地文件的内容做快照并保存到Git 的对象库,实际上就是一个包含文件索引的目录树,像是一个虚拟的工作区。在这个虚拟工作区的目录树中,记录了文件名、文件id等状态信息存储在index中,文件的内容并不存储其中,而是保存在 Git 对象库(.git/objects)中,文件索引建立了文件和对象库中对象实体之间的对应。
总之:当对工作区修改(或新增)的文件执行 “git add” 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID 被记录在暂存区的文件索引中
补充:git ls-files -s 查看暂存区的内容,暂存区的内容不会自动变化,即使是提交到了版本库也不会影响暂存区的内容,只能通过以下指令进行清除
2 暂存区 => 工作区 git reset – files
用来撤销最后一次git add files(因为每git add file一次,暂存区的文件都会被更改一次),你也可以用git reset 撤销所有暂存区域文件。
3 暂存区 => 版本库 git commit -m “注释内容”
当执行提交操作时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树
工作区和版本控制区
我们采用更细粒度的划分探究底层原理
① echo “xxx” | git hash-object --w -stdin 将xxx的内容写入objects(版本区)内,路径:前两个字母作为objects中的目录,后面的字母组成文件名
执行第一行命令时向git中存储“test content”,第二行返回生成的内容(第一个是只生成内容的哈希码,第二个是生成且保存到文件objects中去)
随后我们发现,.git/object下会生成相应文件夹,这也代表写入成功;其中文件夹名是上面第二行返回哈希码的前两个字母,文件名是后面的哈希吗;通过文件名(哈希码)可以获取文件的内容和类型,所以git对象是键(hash)值(blob类型的数据)对类型的数据
git cat-file -p 哈希码:如果是写入到objects中的哈希码就能通过该命令读取到文本值:test content
② git hash-object --w ./test.txt 将当前命令行目录下的test.text文件写入objects中
git cat-file -p 文件哈希码:这个命令可以读出原文件的值
经查看写入文件结果就是文件本身的内容 ,如果我们此时通过vim修改并保存了这个文件objects的内容还不会变化,我们必须再次执行写入命令将修改后的test文件再次写入,原有的内容会被覆盖同时新内容也会被添加进去;
以上操作是片面的,有以下不足:1 无法记住文件所对应的每一个版本极其哈希值; 2我们只是保存了文件的内容但没有保存文件的名字; 3当前操作都是在本地数据库进行的(当前操作是直接由工作区 => 版本库),不涉及暂存区;综上所述,git对象由于无法明确的描述每个项目的信息(项目名,版本,文件内容),且本质上一个git对象代表一个唯一的文件(初始化写入两个文件代表两个git对象,我们分别对这两个文件修改了2次和3次并重写写入这个文件到objects达到更新效果,最后git对象数目为2+3=5个,由此可证以上结论),git对象无法作为项目快照,git只是一个文件对象版本
树对象
树对象可以解决文件名保存问题,允许我们将多个文件组织在一起,Git用了类似UNIX操作系统的方式存储内容,所有的内容均以树对象(tree)和数据对象(git)的方式进行存储,其中tree对应了目录下,git代表了数据项,一个数对象代表了一条或多条记录,每条记录其实就是一个指向git或tree子树的指针;我们可以通过update-index,read-tree和write-tree等命令构造树对象并将其塞到缓冲区
最开始暂存区也是存在的,只不过为空,git ls-file -s可以查看暂存区内容(最初里面的内容为空;
创建缓存区并向其添加文件的过程:
以上操作 1 ①生成文件test.txt并将其存入git对象中返回一个键(哈希值)值(文件本身)对,将该文件作为第一个(该文件代表一个项目,注意为②update-index命令生成的是暂存区而不是具体的文件,为test文件的首个版本创建一个暂存区,第一次向暂存区添加内容用add③100644代表一个普通文件 100755代表一个可执行文件 12000代表一个符号链接③通过)④cacheinfo代表着去数据库取对象,如果是在当前文件夹下取对象就不用写任何东西⑤去查看objects下生成的文件,发现test.txt已被加入暂存区⑤git ls-files -s是查看暂存区内容,可见test.text被加入了暂存区⑥查看objects中去查看,只有一个存储test.txt的git对象,也就是说暂存区的数据见名知意,只是暂存存储,不会主动进行提交
2 git write -tree是生成暂存区的对象tree同时提交到版本库中(存储到objects下),我们可以不断的向暂存区进行增删改直到自己满意再进行提交,git对象代表文件的一次次版本,tree对象代表项目的一次次版本,这就是暂存区的作用,因为objects本来就有一个关于test.txt的git对象,后来又接收了一个从缓存区提交上来的test.txt的tree对象,所以有两个对象类型;也就是我们可以不断的在工作区修修补补然后不断的提交到暂存区,最终将确定下来的版本从暂存区提交到版本库,总之git对象代表文件的一次次版,tree对象代表项目的一次次版本
经实验,暂存区提交后里面的数据并不会消失,但如果再次从暂存区进行提交也不会再生成新的树对象,只有在工作区项目发送变化且执行"git add xxx"后暂存区数据更新为最新一次的提交
先将new.txt文件写入一个git ,查看objects数据库
修改test文件后需要在将test写入git,继续查看objects数据库有四个对象
分析objects中的四个对象
此时在再更新一下暂存区,更新test.txt为最新版本,可见工作区与暂存区一致,其中的结果永远是工作区最新的结果
在暂存区首次添加(add)new.text的版本,缓冲区多了一个new.text的git对象;此时暂存区的状态跟工作区是一致的,是可以代表项目的调用版本 ,此时暂存区中已经有两个对象了,但数据库(版本库)中依然只有一个,最后我们用git write-tree来将暂存区最新状态提交到版本库中
最后数据库中有五个对象,其中最后生成的一个是workspace第二个版本的tree对象
其中9d/…是第工作区WorkSpace二个版本的对象,该版本有test.txt的v2和new.txt的v1;
ead-tree:可以把树对象读入到暂存区,我们把第一颗树对象读入进来(暂存区)
我们在暂存区将其生成树对象读入到控制区(正常情况下实际操作中不存在这种情况)
此时控制区的这个树对象的总结构为:
提交对象
以上tree对象依然不是全面的步骤,虽然可以获取到项目的文件,文件数目,文件名等重要信息但我们无法得知这几个版本分别干了那些事;也就是如果能在tree对象上直白的加上注释就好了
提交对象commit的本质是对树对象tree进行封装(只需要传入前8位的tree对象的hash值即可进行封装),添加一些必要的信息如父对象,作者的name和email,被封装为该commit对象的tree对象,封装tree对象时添加的注释内容
注意,第一次提交产生的commit对象不存在父对象,是跟节点,但随后产生的树对象必须有父对象也父对象是它的上一个提交对象
这就是commit的重要属性:链式意味着commit的信息是连成一串的;commit-tree不仅会生成提交对象还会连带与它相应的tree对象提交到本地数据库
总之,项目快照的本质是一个tree对象,项目完整版本本质就是一个提交对象commit,而版本穿梭的本质就是利用了提交对象链式存储的特点;
总结:add add是将git对象直接读入到控制区备份(git对象的生成是增量式的而非覆盖式的),再在从控制区到暂存区,这样用户进行了多次操作涉及到了多个git,直到用户满意进行提交时才形成一个完整的版本tree对象,再将tree通过指令提交到到版本库,但此时的tree对象内容过于空白让人懵逼,于是我们再次拿出加入一些必要的信息将tree封装为commit对象 ,然后通过指令提交到控制区
一个commit对象对应于一个tree对象,真正表示一个版本;
一个tree对象对应多个git对象,对应一个版本快照故对应多个文件;
一个git对象代表一个文件,项目第一次提交时,项目有多少文件就生成多少个git对象,每次修改后再次提交时生成的git对象数量与项目中被修改的文件数目一致;
Git的特点:1 多数操作均为本地环境下完成; 2多数操作以添加为主; 3 git每次存储的是都是版本快照而非差异存储 ;4 git时刻保持数据的完整性; 5 git数据有三种状态①以修改(modified)②已暂存(staged)③已提交(committed)