也许已经不再害怕Git了 ~Git的内部机制~
补充内容
我们最近发布了相关文章的补充说明内容,面向外部公开。我们还发布了视频的归档和资料,如果您更喜欢观看视频的话,强烈推荐您点击这里。
注意:在视频的质疑中,我错误地声称 GitHub 的发布功能是使用注释标记,但这是错误的。实际上在 git 的数据上,它只是一个轻量级标签,发布的内容应该是通过轻量级标签来关联,然后在 GitHub 的应用程序上进行管理。
首先
我已经调查了一年并搁置了内容,但在圣诞节时间表上努力起来了。
想了解Git的内部机制(动机)。
每天使用Git都可以说是理所当然的事情,但是我突然感到担心,因为我不太明白它是如何管理历史记录的。
我认为当你开始接触Git时,经常会产生以下类似的疑问。
-
- どうやってGitは履歴を管理してるんだ?
-
- ブランチ is 何?枝なの?
-
- HEAD is 何?
- よくブランチみたいな複雑そうなものうまく動いてんな。
我不知道这是第几次总结了,但我总结了Git是如何保存历史记录的。
如果你了解Git的数据存储机制,或许在遇到Git操作的困难时可以更容易地解决问题。
突然得出结论
-
- Gitのリポジトリは Gitオブジェクト の集まりで、単なる**有向非巡回グラフ(DAG)とポストイット**で履歴を管理している
-
- コミットは差分ではなく、スナップショット。
- gitの数あるコマンドは、最終的にDAGへのGitオブジェクトの追加、ポストイットの移動を行なっているだけ。
让我们来看一下Git的内部数据结构,但是需要理解一些重要的概念。这些概念是Git对象和引用。意识到它们的存在会让理解变得更容易。
Git对象
Git对象的特点如下所示。
-
- すべてzlibライブラリで可逆圧縮されたもの
-
- SHA-1ハッシュによって識別子がついている
-
- Gitオブジェクトは4種類
-
- 「./git/objects」ディレクトリを見ると確認できる
- イミュータブル。一度作られたら、なかなか消えない
blob、tree、commit和tag(仅限于注释标签)是Git的对象。
这个Git对象可以很容易地确认。这是我项目的快照。.git/objects目录下的内容就是Git对象。1
我认为可能出现了一个类似哈希值的文件。
后面会提到,使用git命令可以确认是哪个Git对象以及其中写了什么。
Git对象是通过将对象的类型(如 blob、tree 等)等信息与对象本身的内容连接在一起,然后使用可逆压缩算法 zlib 进行压缩,并对其进行哈希运算,因此如果内容不变,则会得到相同的哈希值。
请将这点牢记。
我们将分别看一下以下的blob、tree和commit。
由于Tag对象具有特殊性,因此在参考资料中进行说明会更容易理解,因此将在参考资料中进行解释。
blob对象
blob是指文件数据的实际内容,例如文本数据、图片数据等。它不包含文件名等信息。
我试着看一下里面有什么。
由于Git对象本身经过zlib库的可逆压缩,因此直接打开文件没有意义。但是通过git cat-file -p <Git对象的哈希值>命令可以查看Git对象的内容。
此外,通过 -t选项可以查看Git对象的类型,例如commit、blob、tree等。
git cat-file -p e59c6f
version: '3'
services:
mysql:
image: mysql:8.0.22
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: xxx
MYSQL_DATABASE: xxx
MYSQL_USER: xxx
MYSQL_PASSWORD: xxx
volumes:
- ${PWD}/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
这是一段docker-compose.yml的描述数据,其中没有包含文件名信息。
树对象
这是一个表示目录的Git对象。
它包含一个“文件名”和一个对blob对象或另一个tree对象的引用。
以下是一个类似的形象。
在这里,如果使用 git cat-file 命令确认树对象的内容,您可以确认所引用的Git对象的类型(blob或tree)、该Git对象的哈希值以及文件名(目录名)的信息。
$ git cat-file -p 5f8d20
100644 blob 00a51aff5e5a83d6313f3bd15fadc601a205b66f .gitattributes
040000 tree c7c10265c620e68769281650badc4cc7cc12f444 .github
100644 blob 28e3917784d3e4b7dd3609c0a4aecaf576451bb0 .gitignore
100644 blob 0be9e3f9f8b1f2778e0b266002c087f3024c9c35 Procfile
100644 blob c5ac20facc95a36e4c891920400dfc3d5ee5a5bd README.md
040000 tree 93db3842aa7b3a21546c48c202613039d57720b9 app
040000 tree 61d5c120ca403ee2d5490e4adf0b54c113a9b51f bin
040000 tree d6c08b8aaa435b436f05ad968c778480ac104ad8 data
100644 blob e59c6f67903a62f94cb107a1d89ba75fd5d5e6ff docker-compose.yml
040000 tree c0bc136941e80b1bfbc939eb491caeca3bccd2c9 gradle
100755 blob 4f906e0c811fc9e230eb44819f509cd0627f2600 gradlew
100644 blob 107acd32c4e687021ef32db511e8a206129b88ec gradlew.bat
040000 tree 6fa0cd56f29a9760c54e58e2069b2cd0609283cb mysql
100644 blob f064fb24d549188cf82858bb868bf1c8ebfaf629 settings.gradle
100644 blob 9146af5386451d165fe7b632434f2071acac5280 system.properties
提交物件
提交(commit)对象包含以下信息。
-
- トップレベルのtreeオブジェクトへの参照
-
- コミットしたユーザの情報
-
- タイムスタンプ
-
- コミットメッセージ
-
- 親コミットへの参照
親がないcommitは、initial commit
親が2つあるのは、merge commit
以下是这样的想法。
当你使用 `git cat-file` 命令来查看提交对象的内容时,你可以确认上述信息。
$ git cat-file -p 1aaa
tree 5f8d202e38d52bd011fe7ef881bad5c8ac6edad4
parent e6c06ea543e56eb9230659dd4baf7214730d69b5
author Taro Yamada <xxx@example.com> 1613406374 +0900
committer Taro Yamada <xxx@example.com> 1613406374 +0900
Bean Validationの導入
通过commit所拥有的顶层tree对象中的信息,可以追踪引用的tree对象和blob对象,从而重新构建项目在该时点的目录结构。
当回顾Git对象时,也就是说……
當查看Git物件的連結時,它們形成了一個有向非循環圖(DAG)。
一个Git对象具有对另一个Git对象的引用,但它并不具有循环引用的指向。
参考资料
下面我们来看一下参考信息。
-
- commitオブジェクトのポインタ(ハッシュ値を持ってる)。
-
- 簡単に指し示す先を変更できる(tagはできない)
.git/refs や、.git/HEAD で確認できる。
3種類
branch
HEAD
tag
参考文献是指向提交对象的指针。我提供了项目目录的图示,但参考文件中只写有提交对象的哈希值。
如果你能够想象有便利贴贴在上面的场景,就容易理解了。
我们将对以下内容、分支、HEAD和标签进行讨论。
分支
branch是指向commit(Git对象)的简单指针。
远程分支也是一样的,没有特别的区别。
查看.git/refs/文件夹可以确认分支。每个分支只指向一个提交。
$ tree .git/refs/
.git/refs/
├── heads
│ └── main
├── remotes
│ └── origin
│ └── main
├── stash
└── tags
└── release-1.0
当查看分支参考内容时,可以看到它指向了提交(commit)。
$ cat .git/refs/heads/main
1aaa212c3c4f412309b3c33dc51e78f2810a4ed6 # refs/heads/mainは、commitのハッシュ値を指す
$ git log --oneline -n 1
1aaa212 (HEAD -> main, tag: day17) Bean Validationの導入
标题
这是一个指向正在检出的引用(引用的引用)的指针。有时也直接指向提交对象(分离的HEAD)。
$ cat .git/HEAD
ref: refs/heads/main <- HEADはrefs/heads/mainファイルを指す
HEAD所指示的Commit对象的状态会在项目根目录展开。
标签
标签有两种类型。
一种是轻质标签,另一种是带有注释的标签。
轻量级标签是对提交对象的引用。
注释标签是对标签对象的引用。
前文中提到的标签对象(Git对象)具有对提交对象的引用和附带信息(带注释的消息)。
换句话说,注解标签将附加信息(带注释)如消息等打包成标签对象(Git对象),并指向该标签对象。
从Git对象和引用的角度来看待Git操作。
以上是对Git对象和引用的解释,现在让我们从这个角度来看一下git的操作。
将文件添加到Git仓库中
考虑以下图示的情景。
如果编辑 contacts.txt 文件并通过 git add 将其暂存,那么 Git 对象会发生什么变化呢?
在这种情况下,会创建一个新的blob对象。
由于文件的内容不同,所以哈希值也会不同,将会生成一个与原始contacts.txt(blob对象)不同的blob对象。
提交git
在前面的例子中,当进行 git commit 操作时,会创建一个 tree 对象,接着会创建一个 commit 对象。
此外,main 分支会移动到刚刚创建的 commit 对象。
由于 HEAD 指向 main 分支,所以它自然而然地指向了新的提交。
这里重要的是,内容没有变化的Git对象(包括blob和tree)不会在每次提交时被复制。
如果blob和tree的内容没有变化,它们会得到相同的哈希值。
这样就可以节省数据空间。真聪明。
上图中的新树(红色)引用了之前已存在的blob或树。
git抓取
忘记之前的例子,假设下一个图中的状态。这是fetch之前的本地仓库状态。
当执行git fetch命令时,远程仓库中的git对象和引用将会反映到本地仓库中。然而,本地分支和HEAD等引用不会改变。
快进式合并(git merge fast-forward)
执行 “git merge –ff” 操作将移动分支,不会创建新的 commit 对象。
继续上面的例子,如下所示。
合并Git(非快速前进)
再假设一个新的情况如下。
假设在本地创建了提交对象,并稍后进行了提取(fetch)。
您考虑将远程仓库的新更改(即提交对象)合并到本地分支。
然而,已经创建的commit对象是不可变的,因此无法进行更改。
在这种情况下,我们只能进行git对象的添加或引用的移动。
即使移动引用,也无法合并两个更改,因此我们会创建一个新的commit。这就是合并提交。
执行git merge origin/main后,将生成一个新的commit对象,其中包含两个分支的差异。
Git rebase: 使用Git重新定位基线
這也是假設了一個新的情況。
當有一些本地的改變已經提交、並且遠端也有一些提交的情況下,如果我們希望合併這些改變,如果直接進行合併提交,就會造成縫合點的狀態,使得歷史追蹤變得困難,因此有時候會選擇進行rebase。
当对此进行rebase操作时,将会创建新的commit。具体根据rebase的操作方式而定。
再次强调,一旦创建了commit对象,就无法进行修改,因此即使在进行rebase操作时也会创建新的commit对象。
在rabase之后,g、e、d的commit对象会怎样呢?实际上,它们仍然存在于仓库中。它们不会立即被删除,因此可以恢复。
请容我最后再说一句话。
林纳斯·托瓦兹太厉害了!!!!!!4
文献引用
-
- Git for Computer Scientists
-
- Gitの内側 – Gitオブジェクト
-
- Gitの内側 – Gitの参照
- コミットはスナップショットであり差分ではない(GitHubブログ)