自 2005 年诞生以来,git 曾经在开源世界中大受欢迎,咱们中的许多人也在咱们的工作岗位上应用它。它是一个很棒的 VCS 工具,具备很多长处,但易于学习并不是其中之一。对于 git 如果只会死记硬背命令那么要不了多久你就会遗记, 而后一而再而三的背诵, 无疑让人很受打击, 在我看来,相熟应用 git 甚至开始喜爱它的惟一办法是理解它如何在外部工作。
git 命令只是对数据存储的形象, 如果不理解 git 的工作原理,无论咱们在笔记中记忆或存储了多少 git 命令或技巧咱们依然会对 git 的应用感到困惑. 而 git 则是通过形象的命令来裸露它的数据结构的应用办法.
所以这边文章咱们更多的要关注 git 的外部关系 - 数据模型, 当然这篇文章不会波及到 git 的源码.
筹备工作
初始化仓库
为了解说数据模型, 咱们首先要在本人的工作目录下初始化一个空的 git 仓库
git init
git 会告知咱们曾经在以后的目录下创立了一个 .git 目录, 咱们来看看这个 .git 长什么样子.
$ tree .git/
.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
8 directories, 15 files
其中一些文件和目录是不是看着有些相熟, 当初咱们次要还是看 objects
这个目录, 当初它是空的, 然而一会儿咱们就会扭转它.
提交文件
首先咱们创立一个 Main.java
文件
touch Main.java
而后输出一部分内容
public class Main {public static void main(String[] args) {System.out.println("Hello World");
}
}
而后以同样的形式在筹备一个 README.md 文件
touch README.md
向文件中输出以下内容
this is my first java project!
当初 add 并且 commit 他们到仓库
git add .
git commit -m 'Initial Commit'
模型的创立
当初看上去没啥非凡的, 当初咱们回过头来在看看 .git/objects
目录下曾经存在了一些子文件夹以及文件了
.git/objects
|-- 84
| -- 705622ee44f2afbb21087ca7d81fda01fccded
|-- 95
| -- fc1236534b6f73930367f02895467040f47d4a
|-- b0
| -- 81e51f448387e72a3e3551ba8610eedc172e60
|-- f1
| -- a8b89f50a2fd8287578daa2b0374adf3cad8aa
|-- info
|-- pack
6 directories, 4 files
须要留神的是在你的电脑上目录和文件名称和我这里是不一样的.
blob object 的创立
在 .git/objects
下咱们留神到每个目录的名称只有 2 个字符长度,Git 为每个对象生成一个 40 个字符的校验和(SHA-1)哈希,该校验和的前两个字符用作目录名,另外 38 个字符用作文件(对象)名。
当咱们提交一些文件时,git 创立的第一类对象是 blob object, 在咱们的例子中是两个, 每一个blob object 对应咱们提交的每一个文件:
blob object 蕴含文件的快照以及领有文件校验和.
tree object 的创立
git 创立的另外一种对象是tree object
, 在咱们的例子中只有一个, 它蕴含咱们我的项目中所有文件的列表, 其中蕴含调配给它们的 blob object 的指针(这就是 git 如何将文件与 blob object 相关联)
commit object 的创立
最初 git 还创立了一个 commit object, 该对象具备指向它的 tree object 的指针(以及一些其余信息)
)
这个时候在来看以下 objects 目录下的构造就清晰多了
.git/objects
|-- 84
| -- 705622ee44f2afbb21087ca7d81fda01fccded
|-- 95
| -- fc1236534b6f73930367f02895467040f47d4a
|-- b0
| -- 81e51f448387e72a3e3551ba8610eedc172e60
|-- f1
| -- a8b89f50a2fd8287578daa2b0374adf3cad8aa
|-- info
|-- pack
验证模型的准确性
下面画出了模型图, 然而你认为我这个模型是本人猜的吗? 我又是如何确定哪个是 blob object? 哪个是 tree object? 哪个是 commit object 的呢? 接下来就是见证奇观的时刻了.
应用 git log
命令咱们能够查看咱们的提交历史
commit f1a8b89f50a2fd8287578daa2b0374adf3cad8aa (HEAD -> master)
Author: zhu.yang <zhu.yang@xxx.com>
Date: Tue Jan 8 10:12:06 2019 +0800
Initial Commit
依据咱们后面说的命名约定, 咱们能够在 objects 中发现 f1a8b89f50a2fd8287578daa2b0374adf3cad8aa
这个对象.
想要查看文件内容咱们不能简略的应用 cat
命令, 因为这些不是纯文本文件, 然而好在 git 给咱们提供了一个 cat-file 命令
git cat-file commit f1a8b89f50a2fd8287578daa2b0374adf3cad8aa
能够通过它获取到 commit object 中的内容
tree 95fc1236534b6f73930367f02895467040f47d4a
author zhu.yang <zhu.yang@xxx.com> 1546913526 +0800
committer zhu.yang <zhu.yang@xxx.com> 1546913526 +0800
Initial Commit
从下面能够看到 commit 指向 tree object 并且咱们能够应用 git ls-tree
命令来查看下其中的内容
git ls-tree 95fc1236534b6f73930367f02895467040f47d4a
正如咱们说意料的一样, 其中蕴含了指向 blob object 的文件列表
100644 blob 84705622ee44f2afbb21087ca7d81fda01fccded Main.java
100644 blob b081e51f448387e72a3e3551ba8610eedc172e60 README.md
如果想要查看 Main.java 中的内容则应用 cat-file
命令即可
git cat-file blob 84705622ee44f2afbb21087ca7d81fda01fccded
咱们能够看到其中返回了 Main.java 文件的内容
public class Main {public static void main(String[] args) {System.out.println("Hello World");
}
}
下面就是当咱们创立并提交了一些文件的时候就会产生的事件. 同时也验证了咱们模型的准确性.
批改文件时模型的扭转
当初咱们批改一下 main.java 而后从新提交一下
正如咱们看到的一样,git 以快照的形式为 Main.java
新建了一个 blob object, 因为 README.md
没有被批改, 因而不会为其创立新的 blob object. 而且git 会重用现有的 blob object.
当初,当 git 创立一个 tree object 时,调配给 Main.java
的 blob 指针会被更新,并且调配给 README.md
的 blob 指针将放弃与前一个提交树中的雷同。
)
在最初,git 创立一个 commit object 并指向它的 tree object. 同时还有一个指向它的父提交对象的指针(每个提交除了第一个提交至多还有一个父提交)
到当初为止咱们曾经晓得了 git 是如何解决文件的新增以及编辑的, 惟一还遗留的就是如何解决删除了, 咱们先删除 Main.java:
请留神上图中红色的连线, 咱们发现删除同样也是非常简单, 只须要删除 tree object 指向 blob object 的指针即可. 在这种状况下咱们在新的提交中删除了 Main.java, 因而咱们的提交的树对象不再具备指向示意 Main.java 的 blob object 的指针.
模型对文件夹的解决
咱们提供的这个数据模型还有一个附加性能 -tree object 是能够被嵌套的(它们能够指向其余树对象), 你能够这样想: 每个 blob object 代表一个文件, 每个树对象代表一个目录, 所以如果咱们有嵌套目录, 咱们就有嵌套的 tree object.
因为下面的图曾经是提交屡次后果画进去的了, 再在下面的根底上画构造就不是那么清晰了, 这次我从新初始化一个仓库来演示, 当初该仓库下存在存在的数据如下:
|-- README.md
`-- app
`-- user.json
而后提交, 最初能够看到如下的数据模型
Git 应用 blob object 以及 tree object 来重现我的项目的文件夹构造. 到这里我置信你必定对 git 的数据模型有了较为深刻的理解, 它真的是很简略, 我置信基于它再去学习 Git 肯定会是事倍功半.
总结
- 创立一个提交的时候 git 会新增 blob object,tree object,commit object 并会造成链路图
- 嵌套的 tree object 用来示意文件夹
- git 从复用 blob object
- 除了第一个提交之外, 每一个提交都有一个父提交