git原理之一:数据模型

git,代码管理之神器,众猿之良友。身出名门,构思巧妙,应用于各大小项目之间,受关注度颇为广泛

coder,苦猿也,终日代码为伴,左java, 右python, 仰观架构,俯视bug,神游于指令之间,精驰于算法内外,偶有心得,敲键不止,神神惶惶,战战兢兢,代码所得之难如此,稍有误删,前功尽弃,嚎啕戚然。

今有git作为辅助,众猿皆喜,心无挂碍,意有所托,代码无常损失再无畏也,今以拙笔,概述git原理,助众友猿,善用此神器

注:本文写作过程中,引用参考了不少优秀文章,比我描述更为清楚的,大有人在,参考的资料,都放在ref小节

1. git目录解析

古语有云:一生二,二生三,三生万物。三,万物之始也,git也不能免其道

打开一个git项目,我们会发现其中有个.git目录,展开.git目录,还会发现一个index文件。示例如下:

├── .git
│ ├── HEAD
│ ├── config
│ ├── description
│ ├── hooks
│ ├── index
│ ├── info
│ ├── objects
│ └── refs
├── a.txt
├── b.txt

这些目录非常重要,用于记录项目文件,提交记录,日志等,实现代码的提交,回滚,分支等重要工作。git根据用途,把这些目录划分为三种:

  • working directory。工作目录,即项目文件本身。如图中的.git目录之外的文件,都属于working directory
  • staging area,指的是.git/index文件。这是个二进制文件,记录着文件提交过程中的临时信息,保证数据提交前后的完整性
  • repository。即.git目录下除index的所有文件,其中记录着文件提交后的信息,包括项目文件的修改历史,日志等

一个完整的git提交流程,被划分为三个步骤:

  • 在working directory修改了某些文件
  • 把修改的文件提交到staging area
  • 执行commit,把staging area中的文件永久更新到repository

git提交过程如下:

2.新增文件

让我们来一步步的执行文件提交, 进而了解git的执行原理。虽然我们常用的指令是add, commit,但这两个指令是众多底层指令的封装,并不能很好的观察文件的变化。

在这里我们采用底层指令,来逐步展示文件的提交过程

让我们准备一个git项目。首先在任意路径创建一个目录gittest。并生成两个文件

mkdir gittest
cd gittest
echo $"File A">a.file
mkdir subdir
echo $"File B">subdir/b.file

然后把目录初始化为git项目

git init .

生成后的文件结构为:

$ tree -a

.
├── .git
│ ├── HEAD
│ ├── config
│ ├── description
│ ├── hooks
│ │ ├── applypatch-msg.sample
│ │ ├── commit-msg.sample
│ │ ├── fsmonitor-watchman.sample
│ │ ├── post-update.sample
│ │ ├── pre-applypatch.sample
│ │ ├── pre-commit.sample
│ │ ├── pre-push.sample
│ │ ├── pre-rebase.sample
│ │ ├── pre-receive.sample
│ │ ├── prepare-commit-msg.sample
│ │ └── update.sample
│ ├── info
│ │ └── exclude
│ ├── objects
│ │ ├── info
│ │ └── pack
│ └── refs
│     ├── heads
│     └── tags
├── a.file
└── subdir
    └── b.file

2.1 创建blob object

我们现在要把a.file和subdir/b.file提交到git的repository之中。在repository中,文件并不是直接拷贝过去,而是生成一种叫做blob object的文件,repository 管理的是blob object。

blob object和项目文件是一对一的方式,一个项目文件,就有一个blob object

$ git hash-object -w a.file subdir/b.file

6d8b18077cc99abd8dda05a6062c646406abb2d4
c512b6c64656b87ea8caf37a32bc5a562d797745

该命令生成了blob object文件,并拷贝到.git目录下,另外输出了两个哈希值,我们来看看.git/objects目录:

$ tree .git/objects

.git/objects
├── 6d
│ └── 8b18077cc99abd8dda05a6062c646406abb2d4
├── c5
│ └── 12b6c64656b87ea8caf37a32bc5a562d797745
├── info
└── pack

文件的哈希值前两位被当作目录,后面位数被当作blob object的文件名

我们来检查下blob object文件的内容

$ git cat-file -p 6d8b18077cc99abd8dda05a6062c646406abb2d4

File A

内容和我们的a.file一摸一样

2.2 提交到staging area

生成了blob object之后,要把这项变动通知到staging area中,也就是往.git/index文件中写入内容。

我们首先来看下.git/index的内容

$ git ls-files --stage

空空如也

现在我们来空瓶装好酒,把blob file记录到index中

$ git update-index --add --cacheinfo 100644 6d8b18077cc99abd8dda05a6062c646406abb2d4 a.file

$ git update-index --add --cacheinfo 100644 c512b6c64656b87ea8caf37a32bc5a562d797745 subdir/b.file

再来看看我们的酒瓶

$ git ls-files --stage

100644 6d8b18077cc99abd8dda05a6062c646406abb2d4 0    a.file
100644 c512b6c64656b87ea8caf37a32bc5a562d797745 0    subdir/b.file

再用git status看看

$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   a.file
    new file:   subdir/b.file

是不是我们在git add之后,看到的熟悉输出呢?

没错,git add其实一共就干了两件事,首先把要提交的文件生成blob object, 其次把改动记录在.git/index文件中

2.3 创建tree object

现在staging area已经更新了,下一步要往repository写入了。在写入之前,得先创建tree object。

$ git write-tree

9a61991471b755f7aa66f95a8d26c03fcb80ad59

生成了一个类似前面blob object的哈希值。我们来看看.git/objects下面新增了什么内容

$ tree .git/objects

.git/objects
├── 08
│ └── d38f10799ada5673b3b9879e085bb7ffce8cd3
├── 6d
│ └── 8b18077cc99abd8dda05a6062c646406abb2d4
├── 9a
│ └── 61991471b755f7aa66f95a8d26c03fcb80ad59
├── c5
│ └── 12b6c64656b87ea8caf37a32bc5a562d797745
├── info
└── pack

可以看到,新增了两个文件夹,把目录名和文件名拼凑在一起,可以对应两个哈希值:

  • 9a61991471b755f7aa66f95a8d26c03fcb80ad59
  • 08d38f10799ada5673b3b9879e085bb7ffce8cd3

9a61991471b755f7aa66f95a8d26c03fcb80ad59是刚生成的tree object。那另外一个呢?

我们看一下9a61991471b755f7aa66f95a8d26c03fcb80ad59的文件内容:

$ git cat-file -p 9a61991471b755f7aa66f95a8d26c03fcb80ad59

100644 blob 6d8b18077cc99abd8dda05a6062c646406abb2d4    a.file
040000 tree 08d38f10799ada5673b3b9879e085bb7ffce8cd3    subdir

神不神奇?里面竟然有一个08d38f10799ada5673b3b9879e085bb7ffce8cd3。而且从第二列我们可以看出,类型也是一个tree object

tree object其实对应着文件目录。当我们提交文件到git中时,除了文件本身提交,文件的组织关系(即目录)也要提交。比如例子中,b.file属于subdir, 那么subdir这个文件目录也要提交。除了subdir, 当前的目录,也会生成tree object提交

2.4 提交到repository

现在tree object也有了,万事俱备,该提交到repository了

$ echo "first commit"| git commit-tree 9a61991471b755f7aa66f95a8d26c03fcb80ad59

51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6

commit-tree后面的参数是顶层目录的tree object

commit-tree执行后,输出了一个新的哈希值51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6。聪明的你已经意识到了,会不会是另一种新的object呢?

我们看看.git/objects目录吧

$ tree .git/objects

.git/objects
├── 08
│ └── d38f10799ada5673b3b9879e085bb7ffce8cd3
├── 51
│ └── a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6
├── 6d
│ └── 8b18077cc99abd8dda05a6062c646406abb2d4
├── 9a
│ └── 61991471b755f7aa66f95a8d26c03fcb80ad59
├── c5
│ └── 12b6c64656b87ea8caf37a32bc5a562d797745
├── info
└── pack

还真是。这种新的object被保存到了objects目录下,这是一个commit object。每次提交,git都会生成一个commit object。

我们再来看看具体内容:

$ git cat-file -p 51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6

tree 9a61991471b755f7aa66f95a8d26c03fcb80ad59
author gerald <xixisese@gmail.com> 1632362835 +0800
committer gerald <xixisese@gmail.com> 1632362835 +0800

first commit

里面记录着我们的tree object。作者,提交时间,以及我们的日志。

commit object是提交时生成的object,记录着顶层tree object, 提交者,提交时间,提交日志等信息

至此,commit object生成,我们的提交工作就完成了,我们可以用git log查看我们刚才提交情况:

$ git log --stat 51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6

commit 51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6
Author: gerald <xixisese@gmail.com>
Date:   Thu Sep 23 10:07:15 2021 +0800

    first commit

 a.file        | 1 +
 subdir/b.file | 1 +
 2 files changed, 2 insertions(+)

2.6 小结:

回顾前面的提交流程,我们小结如下:

  • git的提交过程,就是生成blob object, tree object和commit object的过程
  • 提交过程中,数据流向是从working directory, 到staging area, 再到repository

3. 修改文件

前面我们新增了两个文件,现在我们修改b.file, 再看一次与新增文件有什么不同

首先我们修改文件b.file

$ echo $"File B has modified" >> subdir/b.file
$ cat subdir/b.file

File B
File B has modified

接下来我们提交b.file

  1. 创建新的blob object
$ git hash-object -w subdir/b.file

4b031c51add476b2f8c7a0f3b26239d01b60359f
  1. 更新到staging area
git update-index subdir/b.file
  1. 创建新的tree object
$ git write-tree

0cfd8b8e3fc7dce82d64f7b2ba8ed26f522f03f3

查看tree object的内容:

$ git cat-file -p 0cfd8b8e3fc7dce82d64f7b2ba8ed26f522f03f3
100644 blob 6d8b18077cc99abd8dda05a6062c646406abb2d4    a.file
040000 tree 7676ddb3411cfb76a5e59edc3af8ca7547ee3410    subdir

$ git cat-file -p 7676ddb3411cfb76a5e59edc3af8ca7547ee3410
100644 blob 4b031c51add476b2f8c7a0f3b26239d01b60359f    b.file

可以看到tree object依然包含a.file,虽然a.file并没有改变。也就是说,每次新的tree object都包含整个项目文件。

项目文件夹改变后,会生成新的tree object,如图,b.file的内容改变后,除了a.file对应的blob object保持不变外,b.file对应blob boject, subdir和当前路径对应的tree object都新生成了

  1. 提交tree object到repository
$ echo "second commit"| git commit-tree 0cfd8b8e3fc7dce82d64f7b2ba8ed26f522f03f3 -p 51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6

024000dad0c2baedccc57a39d8f195abbde93be0

提交的时候,和之前一样,会生成哈希值为51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6的commit object。这里多了一个-p参数,指定了当前commit的parent。

git中,除了第一个commit外,每一个commit都有parent, parent指向上一次的commit。这样就形成一条完整的commit链,回溯提交历史。

开发者可以指定的任意commit作为parent, 产生分叉, 在其基础上开展的后续提交,会成为一个独立的分支。分支的内容,我们后续讲解。

下图中,箭头代表parent,颜色不一的commit object,因为parent不同,就组成了不同的分支。

查看本例中的commit object, 注意其中多了parent字段:

$ git cat-file -p 024000dad0c2baedccc57a39d8f195abbde93be0
tree 0cfd8b8e3fc7dce82d64f7b2ba8ed26f522f03f3
parent 51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6
author gerald <xixisese@gmail.com> 1632367201 +0800
committer gerald <xixisese@gmail.com> 1632367201 +0800

second commit

查看提交日志,我们能看到一条完整的提交链:

$ git log --stat 024000dad0c2baedccc57a39d8f195abbde93be0

commit 024000dad0c2baedccc57a39d8f195abbde93be0
Author: gerald <xixisese@gmail.com>
Date:   Thu Sep 23 11:20:01 2021 +0800

    second commit

 subdir/b.file | 1 +
 1 file changed, 1 insertion(+)

commit 51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6
Author: gerald <xixisese@gmail.com>
Date:   Thu Sep 23 10:07:15 2021 +0800

    first commit

 a.file        | 1 +
 subdir/b.file | 1 +
 2 files changed, 2 insertions(+)

4. 引入reference

reference我们在下一章介绍branch的时候介绍,这里先提一下。

细心的朋友会发现,我们在运行git log的时候,会加上指定的commit id。而我们平时使用git时,直接就git log就可以。为什么呢?

这里就是我们上面说的commit链了,在git中,所有的commit形成了一颗树,或者叫做一条链,链上有各种分叉,每一条分叉都是独立的提交链

那么,当我们执行git log的时候,看的是哪一条分叉呢?我们需要一个默认分叉

我们把最新的commit id设置为默认分叉:

$ echo 024000dad0c2baedccc57a39d8f195abbde93be0 >  .git/refs/heads/master

现在一切正常了:

$ git log

commit 024000dad0c2baedccc57a39d8f195abbde93be0 (HEAD -> master)
Author: gerald <xixisese@gmail.com>
Date:   Thu Sep 23 11:20:01 2021 +0800

    second commit

commit 51a5e5fcbc1a1a6d2d0853417e195cfd6ccac6a6
Author: gerald <xixisese@gmail.com>
Date:   Thu Sep 23 10:07:15 2021 +0800

    first commit

Ref

  1. https://medium.com/hackernoon/understanding-git-index-4821a0765cf
  2. https://medium.com/hackernoon/https-medium-com-zspajich-understanding-git-data-model-95eb16cc99f5
  3. https://medium.com/hackernoon/understanding-git-branching-2662f5882f9
  4. https://hackernoon.com/reset-101-ba05d9e3f2c7
  5. https://git-scm.com/book/en/v2

索引

~~~异次元传送门



《 “git原理之一:数据模型” 》 有 5 条评论

  1. atenolol what is the use of himalaya himcolin gel Among other currencies on the move on Wednesday was the NewZealand dollar, which popped higher after data on domesticretail sales blew past all expectations lasix and sulfa allergy

  2. Body weight did not change significantly in either group from 69 can you get cytotec 6 In a prospective study, Sen found no statistically significant difference in central cornea thickness in 32 pregnant women with matched controls

  3. A large reduction in term of CI AKI was, however, detected for both UFR guided and CVP guided hydration strategies priligy equivalent Although rarer following the widespread use of antischistosomal chemotherapy, abdominal radiographs may show calcium deposits around extensive deposits of calcified Schistosoma eggs in the bladder wall

  4. PUBMED Abstract Du J, Cullen JJ, Buettner GR Ascorbic acid chemistry, biology and the treatment of cancer buy priligy online In this kind of video game, often described as an open world game, there is a difference between action that is required by the game in the course of the narrative and the action that is merely possible within the bounds the game; this further complicates the question of whether the capacity for some types of play should be removed

回复 furosemide 40mg tab 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About Me

一位程序员,会弹吉他,喜欢读诗。
有一颗感恩的心,一位美丽的妻子,两个可爱的女儿
mail: geraldlee0825@gmail.com
github: https://github.com/lisuxiaoqi
medium: https://medium.com/@geraldlee0825