在 Git 上进行 Git 的黑客攻击
首先
大家,你们使用 Git 吗?Git 真的很方便呢。
在我们公司的新人培训中,我们学习的版本控制工具是 SVN,但是还是想用 Git。
尽管大家都在使用,但 Git 在开始时还是有些难以入门。
这篇文章是关于刚开始使用 Git,大概按照别人说的那样执行 git add、git commit 等操作,但对于这个黑盒子般的 Git 不知道发生了什么,所以我们将使用 Git 来确认其工作原理。
学习坐下来
首先,我会简单介绍一下 Git。然后我们会边操作边确认。
与其他版本管理系统相比,Git有哪些不同之处?
Git 是由开发 Linux 内核的 Linus Torvalds 设计的,旨在实现在大规模开发中进行版本管理。
虽然在此之前已经存在版本管理系统,但与这些系统的不同之处在于 Git 在每次提交时都会保存压缩后的数据。
每次提交时保存压缩后的数据非常重要,而传统的版本管理系统则会将与前一个提交之间的变更差异保存为数据。
只保留了修改的差异,当想要回到1000代之前的版本时,系统需要计算1000次的差异,这样会导致处理效率非常低下。(如果只有大约200代之前的数据消失了会怎么样呢?)
Git在每次提交时都进行了数据压缩和保存,因此即使回滚到1000代之前,系统只需要进行一次计算就可以完成。
git add是将文件添加到暂存区,git commit是将暂存区的文件提交到版本库。
在本地 Git 仓库中,存在三个区域。
1. ワーキングツリー
2. ステージ
3. ローカルリポジトリ
如果用《勇者斗恶龙》来比喻这三个领域(只想传达意象),
1. ワーキングツリー
⇨ プレイ中のデータ
(まさに編集中のファイル)
2. ステージ
⇨ ボス戦の前とか、キリのいいとこでセーブしたデータ
(とりあえず動くまで作ったソースコードを今の状態で保存)
3. ローカルリポジトリ
⇨ 大きな分岐の前で、しくったらいやだから別の名前で保存しておいたセーブデータ
(過去の状態に戻すことができるデータの集合)
顺便说一下,我从来没有玩过勇者斗恶龙。
将工作树中的内容提升至舞台的操作是 git add
将存储在舞台中的数据保存到仓库中的操作是 git commit
。
提交文件至git仓库
使用git add命令时,会为每个文件生成一个压缩文件,并存储在Git的仓库中。
这个压缩文件在Git中被称为blob对象。
(当添加了两个文件时,就会生成两个blob。)
此时,blob的文件名是根据文件内容添加了头部,然后通过SHA-1哈希函数生成的16进制40个字符。
在这里重要的是,blob对象不包含任何原始文件名的信息。
当使用git add命令时,还会生成一个称为索引文件的二进制数据。
索引文件中会记录blob对象和实际文件(通过git add命令添加的文件)之间的关联。
提交git
执行git commit命令后,会生成基于索引文件生成的树对象、以及记录了提交消息、父提交等信息的提交对象。
提交对象会与树对象相关联。
只需要一个选项,
这是 Git 执行的操作,我认为你可能不懂。
接下来,我们将通过 Git 来解决 Git 的问题。
动手操作
我们将亲身实际操作来确认。
生成 Git 仓库
环境
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.6
BuildVersion: 18G103
$ git --version
git version 2.23.0
首先,创建一个空的目录,并将其作为仓库。
$ cd ~
$ mkdir repository
$ cd repository
$ git init
现在,~/repository 成为了 Git 仓库。
$ ls -a
./ ../ .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
我们可以看到保存着各种目录和文件。
这次我们想要查找 .git 目录的更改,所以将 .git 目录作为 Git 仓库进行管理。
$ cd .git
$ git init
为了检测.git目录的变化,将当前状态进行提交。
$ git add .
$ git commit -m 'initial commit'
[master (root-commit) 5ebac92] initial commit
15 files changed, 655 insertions(+)
create mode 100644 HEAD
create mode 100644 config
create mode 100644 description
create mode 100755 hooks/applypatch-msg.sample
create mode 100755 hooks/commit-msg.sample
create mode 100755 hooks/fsmonitor-watchman.sample
create mode 100755 hooks/post-update.sample
create mode 100755 hooks/pre-applypatch.sample
create mode 100755 hooks/pre-commit.sample
create mode 100755 hooks/pre-push.sample
create mode 100755 hooks/pre-rebase.sample
create mode 100755 hooks/pre-receive.sample
create mode 100755 hooks/prepare-commit-msg.sample
create mode 100755 hooks/update.sample
create mode 100644 info/exclude
将git add进行更改
接下来,我们将在~repository中创建一个合适的文件,并尝试进行git add。
$ echo "hello world" > hello.txt
$ git add hello.txt
我创建了一个名为hello.txt的文件,其中包含了“hello world”的内容,并将其添加到了git仓库。接下来,我将查看.git文件夹中的变更。
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
index
objects/
nothing added to commit but untracked files present (use "git add" to track)
$ tree objects/
objects/
├── 3b
│ └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
├── info
└── pack
生成了 index 和 objects/,在 objects/ 中可以确认到生成了 3b/18e512dba79e4c8300dd08aeb37f8e728b8dad。
它们分别是索引文件和 blob 对象。
我们来确认一下它们的内容。
$ git hash-object hello.txt
3b18e512dba79e4c8300dd08aeb37f8e728b8dad
$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world
使用 git hash-object 命令,查找 hello.txt 的 blob 对象的名称。
使用 git cat-file -p 命令,查看其内容。
确认确实是 “hello world”。
$ git ls-files --stage
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 hello.txt
这是 index 文件的内容。
我们可以看到有一个名为 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 的 blob 对象和一个名为 hello.txt 的文件名相关联。
如果现在的状态下进行 git 提交的话,
$ git add .
$ git commit -m 'add hello.txt'
[master dfd8190] add hello.txt
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 index
create mode 100644 objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
提交 git
接下来,将已经添加到 Git 的 hello.txt 文件提交到版本库中。
$ git commit -m 'create hello worild'
[master (root-commit) d482b93] create hello worild
1 file changed, 1 insertion(+)
create mode 100644 hello.txt
检查.git文件的更改。
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: index
Untracked files:
(use "git add <file>..." to include in what will be committed)
COMMIT_EDITMSG
logs/
objects/68/
objects/d4/
refs/
no changes added to commit (use "git add" and/or "git commit -a")
$ tree objects/
objects/
├── 3b
│ └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
├── 68
│ └── aba62e560c0ebc3396e8ae9335232cd93a3f60
├── d4
│ └── 82b930517d4f9575a45ee33ee70d9eae454c27
├── info
└── pack
可以看出有很多变化。
首先,查看之前的提交ID。
$ git log
commit d482b930517d4f9575a45ee33ee70d9eae454c27 (HEAD -> master)
Author: *************************
Date: *************************
create hello worild
请检查提交ID是d482b930517d4f9575a45ee33ee70d9eae454c27的内容。
$ git cat-file -p d482b930517d4f9575a45ee33ee70d9eae454c27
tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60
author ************************* 1573050645 +0900
committer ************************* 1573050645 +0900
create hello worild
我们发现树对象的标识为68aba62e560c0ebc3396e8ae9335232cd93a3f60。我们会检查其内容。
$ git cat-file -p 68aba62e560c0ebc3396e8ae9335232cd93a3f60
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt
能够看出在树对象中,已经将提交的 hello.txt 和与之相关的 blob 对象关联起来。
结束
因为可能变得很长,所以我暂时在这里停下。
实际上,我认为有一个图会更容易理解,但是制作起来很麻烦。
对不起。
虽然我不太明白 Git 到底发生了什么,但是当我们确认其内容后,可以更容易地理解其图像。”Git中的Git”比我想象的更有趣,一定要试试看。