说明:本篇章以场景来说明git命令的相关使用--->压缩文件的作用!
一、本地仓库
场景一:提交文件到本地仓库
(1)在当前的工作区创建一个版本库
git init
# 说明:初始化一个仓库(git的版本库)
说明:最好进入一个空目录,执行git init
命令含义:创建一个空的仓库(empty Git repository),可以发现当前目录下多了一个隐藏.git的目录,这个目录是Git来跟踪管理版本库的
注意:千万不要手动修改这个目录里面的文件,容易把Git仓库给破坏了
后续:分析版本库的内容!
(2)把工作区的文件添加到版本库,受Git控制
分两步:
1)使用命令git add <file>,将文件添加到暂存区
注意:add可反复多次使用,添加多个文件(以空格隔开);
2)使用命令git commit -m <message>,将暂存区的内容添加到版本库中
注意:为了避免遇到各种莫名其妙的问题,请确保目录名不包含中文
说明:所有的版本控制系统(包含git),只能跟踪文本文件的改动,比如txt文件,网页,所有的程序代码等等;但图片、视频、word这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道,同时文件的压缩成几何的增长!
文本类型的文件:包括txt、html、css、js、java、python等
文本编码:强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持!
具体测试:我们编写一个readme文件,放到gitdir目录下,因为这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件!
git add readme.txt(工作区的文件) #--->告诉Git,把文件添加到暂存区
# 说明:绝对路径有问题(不是工作区的文件),会报错,后续说明!
git commit -m "版本的说明" #--->告诉Git,把文件真正提交到仓库!
# 版本的说明最好是有意义的,这样你就能从历史记录里方便地找到改动记录,同时也方便别人阅读!
分析:
当我们在某个目录下运行git init命令后,在该目录下便会生成一个隐藏的.git的子目录。它是用来保存元数据以及对象数据库的地方,这个目录可以说是Git的核心; 每次克隆镜像仓库时(clone命令),实际上拷贝的该隐藏目录里的内容而已,然后再根据这个目录恢复工作目录,即在工作目录下生成相应的项目文件和目录。而我们的暂存区也位于这个.git目录之下,名为 index,它一般很小,一般不超过1KB左右,这和它存储的内容是有关系的。
.git内容:在这个目录下,除了index这个暂存区索引文件,还有其他重要的文件和目录,其中有存放数据对象的objects目录(这可以说是我们git的一个仓库了)、存放引用文件的refs目录以及存放指向当前分支的HEAD文件;
底层:和暂存区极其相关的一条命令是
git add <文件名>//将一个文件添加进暂存区
git add可以分两条底层命令实现:
1 )git hash-object <文件名>
2 )git update-index <文件名>
运行第一条命令:git将会根据新生成的文件产生一个长度为40的SHA-1哈希字符串(散列表),并在.git/objects目录下生成一个以该SHA-1的前两个字符命名的子目录,然后在该子目录下,存储刚刚生成的一个新文件,新文件名称是SHA-1的剩下的38个字符;
运行第二条命令:将会更新.git/index(暂存区)索引,使它指向新生成的objects目录下的文件;
问题1:在objects目录下新生成的文件是什么呢?
说明:在这个目录下,git将其下文件分为四种类型,其中有两种是tree类型和blob类型,blob类型文件存储的就是我们运行命令增加的文件加上一个特定的文件头;而tree类型文件就相当于我们系统下的目录了,里面存储的是多条记录,每一条记录含有一个指向 blob 或子 tree 对象的 SHA-1 指针,并附有该对象的权限模式 (mode)、类型和文件名信息,它和blob类型对象不一样,存储的并非文件的内容。
思考:如果生成一个新的文件存入Git仓库中而没有第二步的更新.git/index索引,那么将会发生两种情况:
情况1:如果该文件从未添加进暂存区,也就是将该文件从未更新到index索引中,那么Git是会一直显示未跟踪的文件
模拟流程:选择一个无用的目录进行git init,然后在运行git status,在目录下新建一个test.txt文件并增加内容,运行git status,运行git hash-object d.txt,再运行git status
untracked:未跟踪文件---->尚未添加到暂存区---->index(stage)
说明:此时.git/objects目录下有了bf子目录以及该子目录下4484b6c312ac452d17552f86ce1c61afee09e2文件
--------------------------------------------------------------------
情况2:如果文件曾经添加进暂存区,那么结果会是怎样?那么结果显示的是修改结果未放入暂存区!
测试:模拟下情况:git add,git status ,git commit -m "add test.txt file ,version 1",git status,修改文件,git status
说明:前后两次,一次显示未跟踪,一次显示未放入暂存区,我们可以得出一个结论:即使在.git的仓库中生成了相应的数据对象文件(命令步骤1-->objects目录的内容),如果没有update index更新索引(后一次),索引还是指向.git/objects下旧版本的文件或者文件在index中根本没有索引,git也无法commit;如果我们在第二种情况下,更新索引同时提交,那么提交的test.txt版本是最近的一次add的版本,另外,我们可以看到git add 后更新的暂存区只是一些文件索引,而文件的快照是保存在.git/objects目录下。
如果你在工作目录下再新建一个文件,查看Git状态,Git只显示刚刚新建的文件为未跟踪文件,而Git对我们那个test.txt没有提示(linux哲学-->没有消息就是最好的消息),这时我们 add新建的文件然后提交,提交的文件包括了刚刚新建的文件和test.txt(未做修改的),我们每一次提交时,都无需对曾经git add 的文件(该文件必须之后未曾修改)再进行一次git add 操作,说明每次add后,Git都会在index下更新文件索引,而且该索引是一直存在的,除非使用git rm删除。
--------------------------------------------------------------------------------------------
下一步:理解暂存区,还要弄清楚我们到底提交上去的是什么?这和暂存区有什么联系?
回答这个问题前,我们先回想一下,上文说的.git/objects下文件有四种类型,其中第三种是commit对象
commit对象:指明了该时间点项目快照的顶层树对象、作者/提交者信息(从 Git的 user.name
和user.email
中获得)以及当前时间戳、一个空行,以及提交注释信息。
简单地add增添一个文件后,我们每次执行git commit操作,一般都会在.git/objects目录下生成另外两个文件(这在上文模拟的第二种情况的命令行也提到过),一个是tree对象类型文件,另一个便是commit类型文件,为什么会生成两个文件而不是一个?
这 个commit对象中包含的tree对象SHA-1值指向了最新版本的根目录树对象。这个tree对象是根据你的暂存区而创建的,每个tree都是这样, 所以创建tree前都需将文件或目录添加进暂存区。如果是添加的是目录则会再生成一个子tree对象,再根据目录下的文件分别生成相应的blob普通文件和子tree对象。这个根tree对象中包含的多条记录,简单的概括,分别是修改后添加进暂存区SHA-1改变了的文件或目录(上文说过的git add 分为的两步,其中最后一步是更新修改文件在暂存区的SHA-1值)或者是没修改但曾添加进暂存区,SHA-1值仍不变的文件或目录。Git正是拷贝暂存区的这些目录树和文件的SHA-1等信息到commit对象而生成Git的一个提交,即commit对象,commit对象保存的提交完整的记录了当前所有已跟踪文件的快照,所以你所提交的项目来自于暂存区!
简而言之:文件索引即暂存区,建立了文件和.git/objects目录下的对象实体之间的映射关系,如下图:
核心 :git add 和git commit 的原理!