GIT科普系列4:仓库/缓冲区/工作副本,傻傻分不清楚?
Posted zssure
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GIT科普系列4:仓库/缓冲区/工作副本,傻傻分不清楚?相关的知识,希望对你有一定的参考价值。
背景:
公司内部主要以Git作为版本管理工具,在日常工作中发现大家使用Git很不熟练,而且学习的积极性不高,似乎GIT给人以一种望而却步的感觉。究其根源(个人臆测)有几点:
一、以为GIT相较于SVN新颖很多,是很高端高大上的工具,上手很难;
二、习惯于传统的图形化GUI操作(这应该得利于Windows的全球普及),对于GIT的诸多bash指令没有好感,自动降低了学习和掌握的信心;
三、对GIT整体概念不清晰,或盲目背诵指令,或完全依赖于各种图形化Git工具。
鉴于此,博主希望尽可能的从底层机制来讲解GIT的原理,使得大家能够对GIT有一个宏观的认识,进而打消莫名的恐惧,达到熟练使用GIT进行代码版本管理的目的。本文之前GIT科普系列1:git如何放弃本地working directory的修改,以及回滚、 GIT科普系列2:git代码检出与日常维护、 GIT科普系列3:底层存储机制Internal Objects三篇已经对GIT的底层机制有了一个简单的介绍,此次作为一个总结篇,借助于图解GIT中的示意图再次介绍几个概念,详情如下。
仓库、缓冲区、工作副本:
同样以系列博文GIT科普系列3:底层存储机制Internal Objects中的整体示意图为例,介绍这三个概念。
从上图可以看出 工作副本(之所以叫做工作副本是相对于版本仓库而言的,其实就是你本地的工作目录)、 缓冲区(Stage,或者Index)、 仓库(Commit之后)是三个完全独立的存储空间(当然对于GIT管理系统自身而言,这三个区间的内容都以Content-based方式存储在.git目录下,详情参见博文 GIT科普系列3:底层存储机制Internal Objects)。
下面我们就以 仓库、 缓冲区、 工作副本三个存储空间为基础,详细看一下GIT版本管理的流程,以及常见git指令完成的具体操作。
【备注】: 非特殊说明,下文截图均来自于Visual git guide项目
1. git add
git add指令是日常使用最多的基础指令(当然如果使用图形化GIT工具的话,可能会很少用到,都会集成到commit指令中。所以我一再强调最好使用Git Bash来完成相关操作)。以 仓库、 缓冲区、 工作副本三个存储空间为出发点来看,git add指令实现的功能就是将 工作副本中的非.gitignore排除的files “添加”( 备注:这里虽然常常用添加来描述,但是可能最符合语义的应该是“拷贝”,因为git add指令后会在缓冲区存储空间新增一个二进制BLOB文件,也就是通常认为的快照snapshot)到 缓冲区。该指令中支持Bash中常见的通配符,诸如*.jpg、*.txt等,可以快速筛选要添加或过滤不要添加的文件。
2. git commit
git commit是几乎所有图形化GIT工具最常用的指令。同样以三种存储空间角度出发,git commit就是将 缓冲区的内容“添加”到 仓库中,这里的 “添加”同样具有“拷贝”的含义。
上面介绍的git add和git commit指令都是“添加”类指令,也就是如何向GIT的基于内容检索的文件系统(Content-Basd Filesystem)写入数据。有写入就还要有读取,下面看几个读取的指令:
3. git checkout
由于仓库、缓冲区、工作副本三个存储空间都是以链表的形式来存储记录每一次的改动,因此在读取(上文提到的写入数据也会跟三个存储空间具体所在的不同节点有关,但是总体来说数据写入只要记住一点,都是写入到当前空间对应的当前节点,所以情况比较简单)上需要根据三个存储空间所在的具体节点来进行判别。这也就是git checkout指令复杂的地方,下面分别看几种checkout指令结果图:
指令1:
git checkout HEAD~ files #HEAD~代表HEAD的父节点,即~符号表示指针前向移动
上图所示此刻实现的功能是将当前HEAD(当前分支处于master,分支名是永远指向一个分支的最后一个节点)指向的父节点中 仓库的files文件 读取并覆盖到 缓冲区和 工作副本,此刻这两个存储空间中与 仓库中相同的文件内容保持一致,而往往 缓冲区和 工作副本两个存储空间的内容会比 仓库多,因为很多还未完成的任务没有提交。
指令2:
git checkout maint #maint代表另一个分支的最后一次提交
此刻实现的功能是将maint分支最后节点对应的 仓库内的所有文件 读取并覆盖到 缓冲区和 工作副本。
指令3:
git checkout master~3#~代表前向移动,3代表移动的节点数
同样,此刻实现的功能是将master分支最后节点向前跳跃3个节点对应的 仓库内的所有文件 读取并覆盖到 缓冲区和 工作副本。
【备注1】:上述git checkout指令的三种格式都是实现从仓库“读取并覆盖”缓冲区和工作副本。这里要强调一下“读取并覆盖”。我们以当前态表示缓冲区和工作副本中的文件、以下一状态表示所获取的目标仓库的内容来详细讲解“读取并覆盖”的含义,主要分为几种:
- 1)当前状态与下一状态相同的文件:“读取并覆盖”后没有任何变化
- 2)当前状态存在但下一状态不存在的文件:“读取并覆盖”后删除该类文件
- 3)当前状态不存在当下一状态存在的文件:“读取并覆盖”后最新增该类文件
对于第二种情况要额外小心,因为这会导致你本地未提交处于中间状态的文件丢失,工作白白忙活了。
【备注2】:上述指令3会出现一种状态,叫做detached HEAD,言外之意HEAD指针没有指向任何一个分支(即没有指向任何一个链表的最后节点)。此刻如果提交的话会导致此次提交处于不稳定状态,如果不进行进一步操作,GIT管理系统会在下一次启动垃圾回收时刻将该节点删除。详情如下:
上图在detached HEAD状态下,运行了git commit在git仓库中创建了一个新的编号为2eecb的节点,关联到之前checkout后指向的b325c节点,并以此作为父节点。由上图可以看出,除了HEAD指针指向2eecb节点以外,没有任何分支指向该节点。这就是所谓的detached HEAD状态。(为什么只有HEAD指向某个节点时,会被认为是detached HEAD状态呢?因为HEAD是用来实现多个分支之间跳转的,即多个链表跳转,而不能为某个链表所独有。只有分支才能属于某个链表,而且都制定分支所在链表的最后一个节点)。
此时如果我们执行git checkout master,将HEAD重新移动到master分支时,如下图所示:
此刻上面新建的2eecb节点就变成了GIT仓库链表的 叶子节点, 即该节点没有下一级子节点。对于这种没有被任何分支或者HEAD所指向的叶子节点,GIT垃圾回收会认为该节点无效,因为从理论上来看你是无法再跳转到2eecb节点的(当然直接输入git checkout 2eecb是可以实现这个功能的,谁天天没事记这个是吧?)。
如果你还是希望保留2eecb这个叶子节点,那么要不你就把HEAD指针移过去(但是此刻你就不能再其它分支进行工作了,因为HEAD要永远指向当前工作分支),其实这里HEAD指向2eecb时,可以理解为一个“无名分支”或者“匿名分支”。显然依靠HEAD指针是不合理的,那么只有一种办法,给2eecb叶子节点建立一个分支,即执行git checkout -b new,如下图所示即可。
此刻2eecb叶子节点有了new分支指向,就不会被GIT垃圾回收给清理掉了,当然你又多了一个分支。
4. git reset
除了git checkout是读取类指令以外,还有一个比较很常用的指令,git reset,同样可以实现“读取并覆盖”的功能。
git reset HEAD~3#这个与git checkout的类似,HEAD是当前分支最新节点,~3表示前向移动3个节点
如上图所示:git reset相较于git checkout来说更灵活了一些,增加了–soft和–hard两个参数,可以分别控制“读取和覆盖”的工作区间,可同时覆盖 缓冲区和 工作副本或只覆盖 缓冲区。
【备注】: 仔细观察git reset与git checkout的两个读取指令的示意图,你会发现git reset强大的地方不单单在于新增了–soft和–hard参数,而是git reset是同时操作分支和HEAD,而git checkout只操作HEAD。——这也就是我们之前提到了GIT实现回滚的底层依据,详情参见博文 GIT科普系列1:git如何放弃本地working directory的修改,以及回滚。
总结:
以仓库、缓冲区、工作副本三个独立存储空间为基础,就比较容易理解git的版本管理机制,以及各个指令所实现的功能。在此基础上,再熟背git指令,你就可以自由的、轻松的来管理的文档了。fighting!
作者:zssure@163.com
时间:2016-09-17
以上是关于GIT科普系列4:仓库/缓冲区/工作副本,傻傻分不清楚?的主要内容,如果未能解决你的问题,请参考以下文章