30 天精通 Git 版本控管

 主页   资讯   文章   代码   电子书 

第 05 天:了解储存库、工作目录、物件与索引之间的关系

在使用 Git 版本控管的过程中,有些很基本的观念必须被建立,这样才能更有效率也更有意义的学下去。有清楚且正确的观念支持,不但有助于你学习 Git 指令操作,更重要的是,学习 Git 相关知识也会更加上手。

了解储存库

我们要使用 Git 进行版本控管,很自然的,我们需要一个「版本库」来储存这些版本资讯,而英文的 Repository 就是这个意思,笔者习惯将这个英文翻译成「储存库」,代表用来储存所有版本的一个空间或一个资料夹与一堆档案。

如果有 Git 使用经验的人,应该很清楚,建立储存库有很多方法,如果你要在任意一个资料夹建立一个 Git 储存库,只要输入以下指令就可以建立完成:

git init

我们透过下图建立 Git 储存库的过程来说明,透过这张图我们可以很清楚的知道,当我们在 G:\git-demo 目录下执行 git init 之后,Git 会自动帮我们建立一个所谓的 Git repository 在该目录的 .git 目录下,各位不用怀疑,这个 .git 资料夹,就是一个完整的 Git 储存库,未来所有版本的变更,都会自动储存在这个资料夹裡面。

image

了解工作目录

在上述这个例子裡,目录 G:\git-demo 此时就会自动成为我们的「工作目录」 (working directory)。所谓「工作目录」的意思,就是我们正在准备开发的专案档案,未来都会在这个目录下进行编辑,无论是新增档案、修改档案、删除档案、档案更名、...以及所有其他 Git 相关的操作,都会在这个目录下完成,所以才称为「工作目录」。

我们在操作 Git 相关指令参数的时候,也通常都是在「工作目录」下执行的。

由于在使用 Git 版本控管时,会遭遇到很多分支的状况,所以工作目录很有可能会在不同的分支之间进行切换,有些 git 指令在执行的时候,会一併更新工作目录下的档案。例如当你使用 git checkout 切换到不同分支时,由于目前分支与想要切换过去的分支的目录结构不太一样,所以很有可能会将你目前工作目录下的档案进行更新,好让目前的工作目录下的这些目录与档案,都与另一个要切换过去的分支下的目录与档案一样。

所以,适时的保持工作目录的乾淨,是版本控管过程中的一个基本原则,更尤其是日后要进行合併的时候,这点尤其重要,相关知识我会在日后的文章中进一步说明。

了解 Git 的资料结构

在 Git 裡有两个重要的资料结构,分别是「物件」与「索引」。

「物件」用来保存版本库中所有档案与版本纪录,「索引」则是用来保存当下要进版本库之前的目录状态。

关于物件

所谓的「物件」是一个「特别的档案」,该档案的产生过程很有趣,是将一个档案的内容中取出,透过内容产生一组 SHA1 杂凑值,然后依照这个 SHA1 杂凑值命名的一个档案。

在使用 Git 进行版本控管的过程中,所有要进行控管的目录与档案,都会先区分「目录资讯」与「档案内容」,我们称为 tree 物件与 blob 物件。

其中 blob 物件就是把原本的「档案内容」当成 blob 档案的内容 (注意: blob 物件其实就是一个实体档案),然后再将其内容进行 SHA1 杂凑运算后产生的一个 hash id,再把这个 hash id 当成 blob 档案的档名。由此可知,blob 物件是一个「只有内容」的档案,其档名又是由内容产生的,所以,任何一个单独存在的 blob 档案通常对版本控管没有任何帮助。

另一个 tree 物件,则是用来储存特定资料夹下包含哪些档案,以及该档案对应的 blob 物件的档名为何。在 tree 物件中,除了可以包含 blob 物件的档名与相关资讯,还可以包含其他的 tree 物件。所以 tree 物件其实就是「资料夹」的代名词。

无论 blob 物件与 tree 物件,这些都算是物件,这些物件都会储存在一个所谓的「物件储存区」 (object storage) 之中,而这个「物件储存区」预设就在「储存库」的 objects 目录下,如下图示:

image

详细的物件结构,我们会在接下来的文章谈到。

关于索引

所谓的「索引」是一个经常异动的暂存档,这个档案通常位于 .git 目录下的一位名为 index 的档案。简单来说,「索引」的目的主要用来纪录「有哪些档案即将要被提交到下一个 commit 版本中」。换句话说,如果你想要提交一个版本到 Git 储存库,那麽你一定要先更新索引状态,变更才会被提交出去。

这个索引档,通常保存著 Git 储存库中特定版本的状态,这个状态可以由任意一个 commit 物件,以及 tree 物件所表示。

我们通常不会直接去编辑 .git\index 这个二进位档,而是透过标准的 git 指令去操作这个索引档,对于索引档的操作指令大概有以下几个:

  • git add
  • git mv
  • git rm
  • git status
  • git commit
  • git ls-files

Git 的「索引」是一个介于「物件储存区」 (object storage) 与「工作目录」 (working directory) 之间的媒介。

各位也许已经可以猜到,本篇文章想阐述的这几个观念之间的关系,可以用以下 5 个步骤解释:

  • 要使用 Git 版本控管,你必须先建立「工作目录」与「版本库」。(mkdir, git init)
  • 你要先在「工作目录」进行开发,你可能会建立目录、建立档案、修改档案、删除档案、... 等操作。
  • 然后当你想提交一个新版本到 Git 的「储存库」裡,一定要先更新「索引」状态。(git add, git mv, ...)
  • 然后 Git 会依据「索引」当下的状态,决定要把那些档案提交到 Git 的「储存库」裡。(git status)
  • 最后提交变更时 (git commit),才会把版本资讯写入到「物件储存区」当中 (此时将会写入 commit 物件)。

详细的索引结构与指令操作,我们会在接下来的文章谈到。

注: 由于 tree 的概念跟 directory 很像,所以在看国外原文时,working directory 也经常被写成 working tree!

今日小结

今天探讨的 Git 架构,最重要的还是在「物件」与「索引」之间的关系,因为没有「索引」资讯,Git 就无法建立版本。

而基于「物件」与「索引」的差异,你应该可以发现,「物件」是属于一种「不可变的」 (immutable) 档案类型,任何写入到「物件储存区」的物件,原则上都不会再发生异动,因为所有的物件都是从原本的档案内容产生的。我们也可以说这是一个「物件资料库」 (object database),且这个资料库通常只会增加内容,比较不会有「删除内容」或「异动内容」的情况,只有在执行 git gc 清除垃圾资料时才会删除资料。「索引」则是属于一种「可变的」 (mutable) 索引档,用来记录目前工作目录准备要 commit 的内容。

当你一步一步的接近 Git 核心,慢慢地将模糊不清的抽象概念,转变成具象的观念知识,你就不会再对 Git 感到不安,请继续努力学习,成功就在前方。

参考链接