#Git原理的简单理解

综述:从根本上来讲 Git 是一套内容寻址 (content-addressable) 文件系统,在此之上提供了一个 VCS 用户界面。

-

根据操作来一步步理解

1. 安装tree工具,用于查看文件目录结构,可以有助于对于Git的学习理解。

brew install tree

安装好之后,可以在任意目录下输入tree命令,就可以看到目录以树形结构进行展示,层级关系一目了然。

2. 新建一个干净的文件夹,执行ls -a命令来显示所有文件(包括隐藏文件),可以看到两个文件:

.  ..

不难联想到当我们想返回当前文件的上级目录,通常都会用cd ..命令,而这里就有..这个隐藏文件,所以可以理解为..其实是对上级目录的一个标识文件。

3. 在当前目录执行git init命令,然后再ls -a查看文件情况。

.    ..   .git

多出来一个隐藏文件.git,所有 Git 存储和操作的内容都位于该目录下。如果你要备份或复制一个库,基本上将这一目录拷贝至其他地方就可以了。

4. cd到.git文件里,然后用tree命令显示当前目录结构。一些文件的大概作用我做了简要注释。接下来重点展开的文件,我标注了*号标记。

$ tree
.
├── HEAD                //*  文件指向当前的分支,这个文件后面解释
├── config              //项目特有的配置选项
├── description         //仅供 GitWeb 程序使用,不作深究
├── hooks               //客户端或服务端钩子脚本
│   ├── applypatch-msg.sample
│   ├── commit-msg.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

8 directories, 14 files

5. 了解git add操作:模拟开发流程,在你的项目目录下创建一个新的文件,我这里以README.md为例子。创建好之后在文件中随便写一点内容,然后执行git add .操作,此时再进入.git目录下执行tree命令

$ tree
.
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.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
├── index
├── info
│   └── exclude
├── objects
│   ├── d0
│   │   └── d37ed92ba0227dc7c3b9a5cb2a8bfa6a04a6f1
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

9 directories, 16 files

会发现在objects目录下多出来了一些东西,这就是由我们刚刚的git add .操作所生成的。那么这些文件代表什么呢。其实这个文件是Git为每份内容生成的一个文件,取得该内容与头信息的 SHA-1 校验和,创建以该校验和前两个字符为名称的子目录,并以 (校验和) 剩下 38 个字符为文件命名 (保存至子目录下)。
简单来说,就是git通过一种算法将我们提交的内容进行了计算,得到了一串数字,然后数字的前两位作为一个目录名,剩余的字符串作为文件名。
查看一下该文件的内容,可以通过 cat-file 命令。该命令是查看 Git 对象的瑞士军刀。传入 -p 参数可以让该命令输出数据内容的类型,注意完整的文件名是目录名+文件名:

$ git cat-file -p d0d37ed92ba0227dc7c3b9a5cb2a8bfa6a04a6f1
#GitTest

我们添加的内容就是存放在这个文件中了。

6. 了解git commit: 在刚刚操作的基础上,执行git commit -m操作,同样观察.git目录结构, 我提交的描述是“。

$ tree
.
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.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
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── master
├── objects
│   ├── 5f
│   │   └── 3a2c5612b3687a8963fe76e46305b0c7ba5724
│   ├── cf
│   │   └── 0359c6428293e0b717f02292ef191ccc590592
│   ├── d0
│   │   └── d37ed92ba0227dc7c3b9a5cb2a8bfa6a04a6f1
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── master
    └── tags

14 directories, 22 files

objects目录下多了两个文件,我们一个个的来看:

$ git cat-file -p 5f3a2c5612b3687a8963fe76e46305b0c7ba5724
tree cf0359c6428293e0b717f02292ef191ccc590592
author liuyiyi <liuyiyi@lyydeMacBook-Pro.local> 1503989030 +0800
committer liuyiyi <liuyiyi@lyydeMacBook-Pro.local> 1503989030 +0800

first commit

这个文件的内容,就是我们git commit -m中的描述信息,注意他有一个tree cf0359c6428293e0b717f02292ef191ccc590592,这个是指向了这次提交时的项目根目录,什么意思看看就知道了:

$ git cat-file -p cf0359c6428293e0b717f02292ef191ccc590592
100644 blob d0d37ed92ba0227dc7c3b9a5cb2a8bfa6a04a6f1    GitTestREADME.md

我们进行commit时项目目录的文件存在状况就是只有一个文件GitTestREADME.md,所以如果我们进行指定版本的代码回滚时,就会根据这个值来置回当时根目录中文件的存在状态。
剩下一个文件是什么的呢:

$ git cat-file -p cf0359c6428293e0b717f02292ef191ccc590592
100644 blob d0d37ed92ba0227dc7c3b9a5cb2a8bfa6a04a6f1    GitTestREADME.md

原来这就是上面我们在有关commit描述文件中发现的指向根目录的那个文件。

7. 继续添加新文件再执行一次git add.git commit -m操作,一个命令指向完就观察一次.git文件的变化状况, 我这里列出最终的结果状态, 过程就省略了:

$ tree
.
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.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
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── master
├── objects
│   ├── 58
│   │   └── 7a55b8f052d0a74bd592ffe7c8c5ff8ccc06c5
│   ├── 5f
│   │   └── 3a2c5612b3687a8963fe76e46305b0c7ba5724
│   ├── 92
│   │   └── a36d18afcce6bdda094c8360551051695fc842
│   ├── c9
│   │   └── c50f0b74a03f5be47d97770a2d11e23983d425
│   ├── cf
│   │   └── 0359c6428293e0b717f02292ef191ccc590592
│   ├── d0
│   │   └── d37ed92ba0227dc7c3b9a5cb2a8bfa6a04a6f1
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── master
    └── tags

17 directories, 25 files

多出来三个文件,拿到文件内容,可以看到和之前第一次文件里存储的东西都是同样的,一个是git add提交的内容,一个是git commit -m的内容,一个是当前根目录。需要注意的是这个保存了git commit -m内容的目录,它与第一次commit有一些差别:

$ git cat-file -p 92a36d18afcce6bdda094c8360551051695fc842
tree 587a55b8f052d0a74bd592ffe7c8c5ff8ccc06c5
parent 5f3a2c5612b3687a8963fe76e46305b0c7ba5724
author liuyiyi <liuyiyi@lyydeMacBook-Pro.local> 1503991626 +0800
committer liuyiyi <liuyiyi@lyydeMacBook-Pro.local> 1503991626 +0800

second commit

多了一个parent 5f3a2c5612b3687a8963fe76e46305b0c7ba5724, 从字面就能理解是啥意思,指向它前一个commit,也可以理解为前一个节点, 可以求证一下:

$ git cat-file -p 5f3a2c5612b3687a8963fe76e46305b0c7ba5724
tree cf0359c6428293e0b717f02292ef191ccc590592
author liuyiyi <liuyiyi@lyydeMacBook-Pro.local> 1503989030 +0800
committer liuyiyi <liuyiyi@lyydeMacBook-Pro.local> 1503989030 +0800

first commit

由于第一次commit它是没有父节点的,所以没有parent, 而之后的commit都是有父节点的, 所以都会有parent这一行。

8. 回过头来讲讲HEAD, refs两个文件。HEAD众所周知是指向当前分支,但是它是怎么指向的呢,看看他的内容就知道了,要切记,HEAD本质上是一个文件!以上所有的东西都是文件!!

vim HEAD


ref: refs/heads/master

HEAD的内容是一个文件路径,所以其实不是说HEAD指向了当前分支,而是它指向了一个文件的路径.根据这个文件路径看看文件内容:

vim refs/heads/master

92a36d18afcce6bdda094c8360551051695fc842

这个路径下的文件内容,就是第二次commit的文件名称, 所以准确来说,这个文件指向了当前分支上的当前commit, 而HEAD文件内容是这个文件。如果文件进行回滚操作,refs/heads/master文件中的值会随之改变,变为你回滚到的那个commit.

9. 创建一个新的分支develop,观察.git文件目录的变化:

└── refs
    ├── heads
    │   ├── develop
    │   └── master
    └── tags

会发现refs/headers下面多了一个文件,名为develop,获取develop文件的内容,指向了master第二次commit内容的文件。

vim refs/heads/develop

92a36d18afcce6bdda094c8360551051695fc842

所以基于当前分支创建一个新的分支,其实本质上只是多了一个文件,而这个文件内容就是指向创建分之时最新的那个commit而已。创建分支的代价非常小。

10. 此时将分支切换到了develop上,此时观察HEAD的变化,估计已经可以猜测到HEAD内容发生了怎么样的变化,文件内容将变refs/heads/develop这个路径,也就是指向了当前分支develop

你可能感兴趣的:(笔记)