自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    |-- tags8 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|-- pack6 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 95fc1236534b6f73930367f02895467040f47d4aauthor zhu.yang <zhu.yang@xxx.com> 1546913526 +0800committer zhu.yang <zhu.yang@xxx.com> 1546913526 +0800Initial Commit

从下面能够看到commit指向tree object并且咱们能够应用git ls-tree命令来查看下其中的内容

git ls-tree 95fc1236534b6f73930367f02895467040f47d4a

正如咱们说意料的一样,其中蕴含了指向blob object的文件列表

100644 blob 84705622ee44f2afbb21087ca7d81fda01fccded    Main.java100644 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肯定会是事倍功半.

总结

  1. 创立一个提交的时候git会新增blob object,tree object,commit object并会造成链路图
  2. 嵌套的tree object用来示意文件夹
  3. git从复用blob object
  4. 除了第一个提交之外,每一个提交都有一个父提交