• 【原创】关于Git暂存区的理解


    关于Git暂存区的理解

         暂存区可以说是Git的三大重要的区域之一,另外两个分别是工作目录和Git仓库,所以说对暂存区的深入理解可以帮助我们理解很多Git命令背后隐藏的工作原理。今天,本文将以一名初学者的角度详细讲解Git暂存区,涉及到的Git命令也会作简单的介绍但不会深入。
        在这里,我们先抛出Git暂存区的概念和相关理解,后面会一一说明。
        (1)所谓的暂存区只是一个简单的索引文件而已。
        (2)暂存区这个索引文件里面包含的是文件的目录树,像一个虚拟的工作区,在这个虚拟工作区的目录树中,记录了文件名、文件的时间戳、文件长度、文件类型以及最重要的SHA-1值,文件的内容并没有存储在其中,所以说  它像一个虚拟的工作区。
        (3)索引指向的是.Git/objects下的文件。 
        (4)暂存区的作用:除非是绕过暂存区直接提交,否则Git想把修改提交上去,就必须将修改存入暂存区最后才能commit。每次提交的是暂存区所对应的文件快照。
        好了结论说到这,上面的结论开始接触时你不一定都能理解,下面开始进行详细说明。
        当我们在某个目录下运行git init命令后,在该目录下便会生成一个.git的子目录,这个目录是隐藏的。它是用来保存元数据以及对象数据库的地方,这个目录可以说是Git的核心,每次克隆镜像仓库时,实际上拷贝的这个目录里的内容而已,然后再根据这个目录恢复工作目录(工作目录:你所git init的目录,但不包含.git这个隐藏的目录),即在工作目录下生成相应的项目文件和目录。而我们的暂存区也位于这个.git目录之下,名为index,它一般很小,一般不超过1KB左右,这和它存储的内容是有关系的。
        在这个目录下,除了index这个暂存区索引文件,还有其他重要的文件和目录,其中有存放数据对象的objects目录(这可以说是我们Git的一个仓库了)、存放引用文件的refs目录以及存放指向当前分支的HEAD文件。
        和暂存区极其相关的一条命令是
    1. 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目录下的文件。
        在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。
     1     #初始化仓库
     2     $git init
     3     Initialized empty Git repository in E:/t/test/.git/
     4     #查看Git当前状态
     5     $git status
     6     On branch master
     7     Initial commit
     8     nothing to commit (create/copy files and use "git add" to track)
     9     #在当前目录下新建一个内容为"this is a test file"的test.txt
    10     $echo 'this is a test file ,version 1'> test.txt
    11     #再次查看Git的状态,Git会提示工作目录下有一个未跟踪的文件
    12     $git status
    13     On branch master
    14     Initial commit
    15     Untracked files:
    16     (use "git add <file>..." to include in what will be committed)
    17     test.txt
    18     nothing added to commit but untracked files present (use "git add" to track)
    19     #此时查看.git/objects目录,只有两个文件夹info和pack
    20     $git hash-object -w test.txt
    21     4617a36485976a90eb72e7020911dea0c892956b
    22     #此时.git/objects目录下有了46子目录以及该子目录下17a36485976a90eb72e7020911dea0c892956b文件
    23     #Git仍然显示test.txt为未跟踪文件
    24     $git status
    25     On branch master
    26     Initial commit
    27     Untracked files:
    28     (use "git add <file>..." to include in what will be committed)
    29     test.txt
    30     nothing added to commit but untracked files present (use "git add" to track)
        (2)如果文件曾经添加进暂存区,那么结果会是怎样?那么结果显示的是修改结果未放入暂存区。
        模拟下情况:git add,git status ,git commit -m "add test.txt file ,version 1",git status,修改文件,git status
     1     #将我们上次的test.txt添加进暂存区
     2     $git add test.txt
     3     #查看状态
     4     $git status
     5     On branch master
     6     Initial commit
     7     Changes to be committed:
     8     (use "git rm --cached <file>..." to unstage)
     9     new file: test.txt
    10     #提交
    11     git commit -m "add test.txt file ,version 1"
    12     #查看当前状态,当前工作目录是干净的,没有什么要提交的了
    13     #此时查看.git/objects目录,多了两个子目录,一个是tree对象,一个是commit对象暂时忽略它
    14     $git status
    15     On branch master
    16     nothing to commit, working directory clean
    17     #继续修改
    18     $echo 'this is a test file ,version 2'> test.txt
    19     #Git显示有一个修改文件未放入暂存区
    20     On branch master
    21     Changes not staged for commit:
    22     (use "git add <file>..." to update what will be committed)
    23     (use "git checkout -- <file>..." to discard changes in working directory)
    24     modified: test.txt
    25     no changes added to commit (use "git add" and/or "git commit -a")
    26     $git hash-object -w test.txt
    27     1ed62f7cc06e6a57cdbaf9a5a5023aa93bd1ffb1
    28     $git status
    29     On branch master
    30     Changes not staged for commit:
    31     (use "git add <file>..." to update what will be committed)
    32     (use "git checkout -- <file>..." to discard changes in working directory)
    33     modified: test.txt
    34     no changes added to commit (use "git add" and/or "git commit -a")
        前后两次,一次显示未跟踪,一次显示未放入暂存区,我们可以得出一个结论:即使在.git的仓库中生成了相应的数据对象文件,如果没有update index更新索引,索引还是指向.git/objects下旧版本的文件或者文件在index中没根本有索引,Git也无法commit。如果我们在第二种情况下,更新索引同时提交,那么提交的test.txt版本是最近的一次add的版本。另外,我们可以看到git add 后更新的暂存区只是一些文件索引,而文件的快照是保存在.git/objects目录下。
        如果你在工作目录下再新建一个文件,查看Git状态,Git只显示刚刚新建的文件为未跟踪文件,而Git对我们那个test.txt没有提示,这时我们add新建的文件然后提交,提交的文件包括了刚刚新建的文件和test.txt,我们每一次提交时,都无需对曾经git add 的文件再进行一次git add 操作,说明每次add后,Git都会在index下更新文件索引,而且该索引是一直存在的,除非使用git rm删除。
        
     1     #创建一个new.txt新文件
     2     $echo 'this is a new.txt file'> new.txt
     3     #添加到暂存区并提交,此时可以看到提示信息中有master b5e4b5c,该数值为commit对象的SHA-1值
     4     $git add new.txt
     5     $git commit -m "add new.txt file"
     6     [master b5e4b5c] add new.txt file
     7     1 file changed,1 file insertion(+)
     8     create mode 100644 new.txt
     9     #通过cat-file命令可以查看对象中存储的内容,可以看到master commit对象指向的是一棵树对象,即代表根目录
    10     $git cat-file -p b5e4b5c
    11     tree 019ab86ed8f73265378a25dc00bff6c085f35cb0
    12     parent 8d50d0c061da5104551bdbaa060e0d4c916411c1
    13     author cposture <cposture@126.com>1418539116+0800
    14     committer cposture <cposture@126.com>1418539116+0800
    15     add new.txt file
    16     #查看树对象根目录的存储内容,可以发现tree对象存储的是指向两个我们创建的文件SHA-1值和其他相关信息,而不是数据本省内容
    17     $git cat-file -p 019ab86
    18     100644 blob 3e1267f7b311e69a691cf2ff5b90b46afaa1a359    new.txt
    19     100644 blob d5210ccb24bd6e8bbc994ade3be3b48a9001f3b7 test.txt
        理解暂存区,还要弄清楚我们到底提交上去的是什么?这和暂存区有什么联系?
        回答这个问题前,我们先回想一下,上文说的.git/objects下文件有四种类型,其中第三种是commit对象。
        commit对象指明了该时间点项目快照的顶层树对象、作者/提交者信息(从 Git的 user.name 和user.email中获得)以及当前时间戳、一个空行,以及提交注释信息。
        简单地增添一个文件后,我们每次执行git commit操作,一般都会在.git/objects目录下生成另外两个文件(这在上文模拟的第二种情况的命令行的第13行注释中也提到过),一个是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目录下的对象实体之间的映射关系,如下图:

     
     
    本文链接:http://www.cnblogs.com/cposture/p/4178003.html

     

  • 相关阅读:
    iOS开发WKWebView Cookie的读取与写入,与UIWebView的Cookie共享
    【拨号】iPhone拨号功能隐藏代码,值得收藏。
    iOS: 获取不变的UDID
    ios 系统设置对应URL
    iOS开发UI篇—多控制器和导航控制器简单介绍
    iOS开发UI篇—以微博界面为例使用纯代码自定义cell程序编码全过程(二)
    iOS开发UI篇—Date Picker和UITool Bar控件简单介绍
    iOS开发UI篇—程序启动原理和UIApplication
    iOS开发UI篇—常见的项目文件介绍
    iOS开发UI篇—以微博界面为例使用纯代码自定义cell程序编码全过程(一)
  • 原文地址:https://www.cnblogs.com/cposture/p/4178003.html
Copyright © 2020-2023  润新知