git使用小手册(二)

本篇文章适合有一定git使用经验的,看完之后再使用git会更加的得心应手;

没有使用过git的同学,可以先看下这个  git使用小手册(一)

先抛三个问题:

1、为什么说git是一个内容寻址的文件系统?

2、为什么git说分支是轻量的?

3、变基和merge有啥区别呢?

如果你对以上问题不清楚,就让我们带着问题,在本文中寻找答案吧!

一、.git文件目录

        用git init初始化git仓库后,会生成一个.git文件夹。你可以把它当成是当前仓库的一个数据库,这里面几乎包含git对当前仓库进行管理时所需要的全部数据,例如有哪些分支、每个分支有多少版本,每个版本的仓库快照,远程仓库信息、钩子文件等。

1、hooks

        hook翻译为钩子,顾名思义这个文件夹包含一些钩子文件,会在某些git命令被执行时触发。默认这个钩子文件是不可用的,具体使用方法在文件内的注释中有说明,比如改个文件名等。

2、info

exclude文件:作为.gitignore文件的扩展用。

refs文件:保存每个分支最新的一次提交的sha-1值。

3、logs

        顾名思义存储git的操作日志,包括提交日志的sha-1值、拉取、合并、创建分支,分支切换、重置等的信息。但是根据git的设计原则,这里只是保存一些引用,相关数据以对象的形式保存到objects文件夹下。

logs/HEAD保存自己所有的git操作日志,

logs/refs/heads/ 保存本地仓库各分支的操作日志,

logs/refs/remotes/ 保存远程仓库的操作日志

4、objects

        数据对象文件夹,git真正存储数据的地方,仓库各版本的数据都被压缩(zlib)和打包(packfile)后放到这里。用文件的形式保存数据,倒是符合Linux一切皆文件的哲学,嗯,有那味了~

这里主要分为三类文件夹:

1)以sha-1值的前两位命名的数据文件夹

sha-1值是个40为的字符串,git取前两位对文件夹进行分类,剩下的38位为数据文件名。

        一般执行git commit后,会生成三个对象保存在这里,commit object、tree object、blob object,这些对象起初被认为是loose(松散,不紧的)对象。为了节省空间,git采用某种算法(类似JVM判断对象是否存活的可达性分析法)判断该对象是否为loose对象,然后采用zlib压缩算法对这些loose对象压缩,然后将结果分别保存到info和pack文件夹中。

2)info文件夹

类似打包文件的索引文件。

3)pack文件夹

压缩打包后的数据文件和索引。

5)refs

        引用文件夹。为了找到每个分支最新的一次提交sha-1值,git采用专门的文件对齐进行存储。这里面包含本地仓库分支、远程仓库分支、标签、储藏等数据最新一次的sha-1,每次对应功能的sha-1发生改变时,git都将最新的数据写入到对应的文件夹内。

一些重要的文件:

config:

        当前仓库的配置文件,包括远程仓库的信息、远程分支和本地分支的对应关系、当前提交者的信息等。具体的配置项可参考git config --help文档。值得一提的是,git有三种配置,OS级别的,当前级别的,当前仓库级别的,优先级是局部大于整体,这个和Linux下的环境变量的优先级也很像。

HEAD:

        表示当前工作区处在哪个分支上。处于哪次提交由refs/heads/ 下对应分支的文件内保存的。

index:

        表示当前暂存区中的文件树内容。该数据将会在git commit时跑到objects文件夹内。

COMMIT_EDITMSG:

        表示当前分支最新一次提交的提交日志。

二、git特点和概念

3.1、git特点

1)分布式的VCS和本地仓库

        在分布式VCS中,每个端都拥有完整的文件副本,能避免单点故障。git的本地库就是一套完整的VCS系统,意味着不需要联网就能进行版本控制。

2)出色的分支系统

a、十分方便创建和操作适合自己的开发分支模型(每个分支都是完全相互独立的工作空间)

b、基于分布式特点和分支系统,创建任何形式的git工作流程(见下文 10)git工作流)

3)先进的设计理念

  1. 拥有工作区、暂存区、本地仓库、远程仓库四种存储空间
  2. 采用引用和实际数据分开的方式保存版本数据。每次提交产生一个提交对象(commit object)和对应的版本快照,(就好比对象的引用和堆中实际存储的对象的关系)

4)速度快

比如分支切换时。

3.2 git核心概念

0)文件状态:

文件本身的状态:new file、modified、clean

git文件管理状态:untracked、tracked、unstaged(修改后),staged、clean

1)git仓库(repository):

        仓库是git的一个术语,一个git仓库包括.git文件夹和被git所管理的当前工作目录。

2)工作区(work directory)

        工作区就是当前用户所能看到文件目录,是某个分支某次提交检出后的物理文件。工作区的文件改动,将同步到暂存区,进而同步到本地仓库。

3)暂存区(stage)

        暂存区表示对当前工作区工作进度的一次临时存储。暂存区只有一个版本,他和工作区一一绑定,当切换分支时,暂存区和工作区都会被重置。

git checkout -- fileName 能撤销工作区的改动,如果暂存区有新的暂存,git会用暂存区的文件重置工作区该文件,可以利用这点,对单个文件进行一次回滚操作,但这是回滚,不能穿梭。

4)本地仓库(local Repository)

5)远程仓库(remote Repository)

6)快照(snapshot):

        相当于给当前项目拍个照片,做个样子定格一样。每次commit时,git保存的是当前项目的快照和引用。为了高效,对于改动的文件保存最新的文件快照,未改动的文件保存之前最后一次修改的引用。

        快照文件内容保存在.git/objects 文件夹下,快照的引用保存在.git/logs 文件夹下。有了快照对象,有了对象的引用,很容易想到git也有对象的垃圾回收机制。

7)git对象

7.1)提交对象(commit object):

        每次执行git commit后,git会在本地仓库上创建一个commit object和对应的的文件版本快照对象,commit中有个指针指向该版本快照对象。

        这个commit object包含了父对象的对象校验和(可能会有多个例如merge时)、当前commit object的对象校验和(这个commit object很适合用链表来存储啊)、提交人信息和提交注释等信息。git在暂存和提交时,会对本次内容计算哈希值(利用SHA-1算法,40位字符串),这个哈希值称为对象校验和

这个对象校验和有三个作用:

1、保证数据的完整性。

2、判断文件是否发生变动

(以上两点取决于哈希算法的特性:即相同的内容总是产生相同的哈希值)

3、当做唯一的指针指向某个版本快照对象。

多个commit object 之间构成一个链表,每个commit object拥有上一个提交对象的sha-1值,和对应的树对象的sha-1值。

7.2)树对象(tree object):

某次提交对应的快照文件树的索引,包含关联的blob对象的sha-1值。

采用 tree 这种数据结构来存储数据。

7.3)数据对象(blob object):

实际存储的文件快照,每个数据对象都有一个唯一的对象校验和(sha-1值)。

8)HEAD指针:

        HEAD指针是个引用。为了让git使用者知道自己当前所处的位置,HEAD指针标识用户当前处在哪个的分支的哪个commit节点上

两个文件保存了HEAD指针:.git/HEAD文件保存当前所处的分支;./git/refs/heads 保存了每个分支的最新提交的sha-1值。

9)分支(branch):

        每个分支都是一个独立的工作空间,各分支之间互相不干扰。由于git先进的设计理念,使得git的分支变得十分快速和方便。传统的VCS创建分支是手动的将某个分支的物理文件复制一份,切换也需要手动切换文件夹,十分麻烦。git分支的创建只是生成一个包含开始节点对象校验和的文件,刚开始只有一个开始节点的对象校验和。分支切换也只是切到另一个分支文件中查询对象校验和,git会帮你根据对象校验和查询文件快照,从而更新当前暂存区和工作目录的文件。

        在.git/logs/HEAD文件中保存当前项目的所有提交和切换日志,

        .git/logs/refs/heads文件夹下,针对每个分支的提交日志都有个单独的文件保存着。

9-1)合并代码-》fast-forward(快进)

        fast-forward是git执行分支合并的一种策略。例如从master分支的某次提交C1开启了hotfix分支,如果从C1点能直接向前走到hotfix最新的一次提交(和JVM的可达性分析法判断对象存活一样),此时hotfix分支和master分支合并时,git只是简单的将master分支的HEAD指针,向前移动到hotfix最新的一次提交,即表示合并完成。整个过程就好像电影快进一样,所以称之为fast-forward。

9-2)合并代码-》三方合并

        三方合并(Recursive,递归)是git执行分支合并的另一种策略。三方合并就是三方的合并,会有三个分支。假设dev1和dev2都开辟自master的C1提交,dev1分支提交了几次代码,dev2分支也提交了几次代码,此时想将dev1和dev2合并,git采取的策略是,用master的C1提交和dev1、dev2中的最新提交来个简单的“三方”合并。

与fast-forward不同的是,他会自动产生一个新的提交快照,该提交称为合并提交

如果在合并时产生冲突,git会自动合并保留这些冲突,等待用户手动解决冲突后,手动提交。

使用工具的好处是工具能提供相对方便的方式来解决这个问题。

9-3)变基VS merge

        首先,rebase和merge对于快照合并结果是一样的,不同的是对于提交日志。变基使得合并后的提交日志是一条线,而合并有明显的产生分支和合并分支的交叉点。他们本质上是对.git/logs中提交日志文件内容的修改。变基是将当前分支的整个提交日志,整体移动到目标分支的最新提交上,可能本来是从master的C1产生开始提交日志,变基后开始提交日志就变成目标分支的最新一次提交了。

如果为了使整个项目的提交日志看起来更整齐,可以使用变基操作:

git checkout -b feature    #假设在dev分支上开辟一个feature分支
some commit                #执行多次提交,功能开发完毕
git rebase dev             #变基到dev。注意此时仍处于feature分支,属于feature在上dev在下的并行提交,要完全合并成一个,还需要merge
git checkout dev           #切换到dev分支
git merge feature          #dev合并feature的代码,属于fast-forward合并
git branch -d feature      #删除该特性分支

之后有新功能时,再次执行上述操作,这样dev的提交日志就是一条线了。如果使用git merge就两条线,创建多个分支就是多条线。

10)git工作流

基于Git 的分布式特性和出色的分支系统,可以相对轻松地实现几乎无穷无尽的工作流。

常见的三种:

1)集中式工作流程(svn式,传统式)

2)集成管理器式工作流程(pr式)

3)独裁者和副官式工作流程(适合大型分布式微服务系统,多模块多子系统时)

五、git如何存储和查找数据的?

        要理解git是如何存储和查找数据的,这里有几个词很关键——三棵树、四个区域、.git文件夹,git实质上是在这几个区域内来回捣腾数据。

        ps:三棵树,指的是当前工作区的树、暂存区的树、本地库的某个提交的树。其中三棵树和四个区域算是逻辑上的结构,.git文件夹才是物理上的结构。

这里以一个基本的操作流程为例:

1、git clone

        克隆会直接将远程仓库的.git文件夹内容直接拉取过来,初始化本地仓库,同时创建一个同名的本地分支track(追踪)远程分支,默认是master分支。然后将该分支的最新提交快照检出到当前工作区,同时重置暂存区。 更新config等相关文件。

2、git add

        之后你会新增文件(untracked)或修改原有文件(modified),此时你的工作区的树和暂存区的树就不一样了,你可以使用git diff [option] 命令查看差异内容。此时如果你执行git add xxx命令会将差异同步到暂存区,使用git status命令,git会告诉你有哪些文件需要提交。此时你可以选择提交或者继续修改。

        当你执行git add时,意味着暂存区的树内容发生了变动,git会用sha-1算法计算新的哈希值。

        值得一提的是,暂存区相当于一次"小的commit",当你对工作区的想法不满意想要回到暂存之前时,可以执行git checkout -- file 等命令,来进行一次所谓的“回滚”。但是注意,这里的回滚不同于版本穿梭,“你滚回去就不能再滚回来啦”,也就意味这你的内容将永久丢失。注意注意!

3、git commit

git commit所做的其实是git存储数据的过程

        提交后,git会生成三个对象——提交对象、树对象、blob对象。这三个对象都保存在.git/objects的某个文件内,为了区分不同的对象类型,git在计算对象的sha-1值时,会先构造一些头部信息,例如  "blob #{content.length}\0",    这个头部信息分三块,第一块为对象的类型,分别为commit、tree、blob,然后将头部信息拼接上实际的数据,从而计算得出“对象校验和”。在文件内容方面,git会根据./git/HEAD文件中的当前分支,找到并更新./git/refs/heads/下对应分支的sha-1值,同时在./git/logs下增加一条操作日志。

        这个提交对象就是个提交日志,其中最重要的信息时父对象的sha-1值和关联树对象的sha-1值,还有少许的提交信息,并不存储真实的文件树快照,每个快照中文件的数据还要交给了树对象来完成。

        这个树对象在git commit时,保存暂存区文件树的快照。树上面有很多树entry,每个entry都是一个引用对象,一个entry基本对应文件树中的一个文件,其中一个很重要的属性是代表文件内容的blob对象的sha-1值为了高效,对于没有改动的文件,git只是保存了原文件的引用,对于改动的文件,git也会计算两个版本之间的差异,将差异保存在上一版本,最新版本总是最新最完整的文件数据。

        最后,git会将经过差异比较后的文件内容,保存为一个blob对象,存放到./git/objects 文件夹内。

        为了进一步节省空间,git会定时对objects文件夹中的文件进行打包,生成一个packfile,这个在上面讲一、.git工作目录时说过。

        所以,经过上面的分析,我们发现,git以链表的形式保存每个分支上的提交对象,一个链接的节点代表一个提交对象,一个提交对象唯一关联一个树对象,树对象以一对一或一对多的形式关联这blob对象。这里没有准备图片,大家可以根据理解自己画一画三个对象之间的关系!!!

4、git branch

        如果用git branch 创建一个分支,实际上只是创建了两个文件

        1、./git/logs/refs/heads/分支名    #准备记录操作日志

        2、./git/refs/heads/分支名           #记录分支最新提交的sha-1值

5、git checkout

        如果用git checkout 分支名来切换分支时,git做了哪些事呢?这其实也是git查找数据的过程,大致如下:

        首先git 会到/.git/logs/refs/heads/分支名 文件中找到最新的一次提交的sha-1,然后git会根据这个提交对象的sha-1值,到objects下找对应的数据对象,取出里面的tree对象的sha-1值,再根据tree对象的sha-1值找到objects中对应的tree对象,根据tree对象和关联的所有的blob对象,直接重置暂存区和当前工作区。

        此外git还会更新HEAD文件中的当前分支名和./git/refs/heads/分支名文件中的当前提交的sha-1值等信息。

        注意,这个时候你未提交的数据将会丢失,但是git早就知道这点,如果使用命令行时git会阻止你切换分支,关于此时如何切换,可以查看 git使用小手册(一) 中的切换分支操作。

6、git reset

        如果使用git reset进行版本穿梭的话,git会将head指针在当前分支的链表上移来移去,找到那次提交的sha-1值,然后按照上面git查找数据的过程到仓库中查找数据,git reset不是有三个参数吗,git会根据不同的参数,选择重置不同的区域树。然后再更新最新提交的sha-1和index等文件信息。

        好了,别的命令就不再说明了,其他命令基本上也都大同小异。

        怎么样,现在是不是对于开头说的“git实质上是在这几个区域内来回捣腾数据”,以及上一篇文章git使用小手册(一)  中说的 “git本质上是一个内容寻址的文件系统”,有了更深的理解了呢?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值