git学习笔记
前言:最近读了《pro git》对git内部原理的阐释,我感觉收益匪浅。正好最近老师让写一遍git的博客,就写一篇读书笔记来和大家分享一下,顺便安利一下《pro git》。
平常开发中所使用的 git push
、 git pull
等命令,都是为了便于用户使用,对操作进行相应集成和封装。通过学习git的内部原理,有利于我们更好地了解 git。
接下来我们一步步从底层一点一点学习,逐步来构建一棵版本控制树。
.git目录
当我们通过git init
对项目初始化后,项目目录下会出现一个 .git
目录。 .git
目录里面存放着进行代码控制所需的所有数据和信息:
其中, objects目录、HEAd文件、index文件(可能刚初始化时不存在)和refs目录是Git的核心组成部分。objects目录存储所有数据内容;ref目录存储指向数据提交对象的指针;HEAD文件指向目前被检出的分支;index文件保存文件暂存区信息。
存取文件
我们在Git仓库中保存文件数据时,它会通过SHA-1 的方式对文件加密和存储,然后返回一个 SHA-1值 。之后我们需要通过这个SHA-1值 来获取文件。 我们来具体操作一下:
- 首先,创建一个 a.txt 文件, 内容为:
hello world
- 通过
git hash-objet -w 文件名
命令来将文件保存到 .git/objects 目录下,并返回一个 key:
$ git hash-object -w a.txt
8f10d293354542a5babf0e66021e462a7ddb588d
可以看到生成的SHA-1值为8f10d293354542a5babf0e66021e462a7ddb588d。
- 查看.git/objects目录
会发现.git/objects 目录中出现了一个 8f目录 ,8f目录内部有一个10d293354542a5babf0e66021e462a7ddb588d文件,目录名+文件名恰好就是 SHA-1值。而这个文件就是我们的 a.txt 文件
- 通过
git cat-file -t SHA-1值 > 文件
获取Git仓库中的文件内容:
git cat-file -t 8f10d293354542a5babf0e66021e462a7ddb588d > b.txt
在我们的项目目录下就出现了b.txt文件,查看b.txt内容:
hello world
文件存取如图所示:
blob对象和tree对象
类似文件系统中的文件和目录,.git/object 中的对象格式有** Blob对象 **和 tree对象。
我们存放的文件的格式为Blob对象,可以通过 git cat-file -t
命令来查看:
$ git cat-file -t 8f10d293354542a5babf0e66021e462a7ddb588d
blob
类似目录是用来管理文件的,tree对象是用来管理我们存入Git仓库的Blob对象的。一个tree对象可以管理多个blob对象和tree对象。通常, Git根据某一时刻暂存区(即index区域)所表示的状态创建并记录一个对应的tree对象。具体操作如下:
- 通过
git update-index
为一个a.txt 创立暂存区(其中 10064表示为普通文件):
$ git update-index --add --cacheinfo 10064 8f10d293354542a5babf0e66021e462a7ddb588d a.txt
.git目录下出现 index文件,用来保存文件暂存区信息:
**
- 通过
git write-tree
命令将暂存区内容写入一个树对象:
$ git write-tree
6d110a67b08a546bbd5b28e59b3298ee1eacc22c
产生了一个SHA-1值为 6d110a67b08a546bbd5b28e59b3298ee1eacc22c 的文件,查看 .git/object 目录:
- 可以通过
git cat-file -t
命令来查看格式, 会发现为tree格式:
$ git cat-file -t 6d110a67b08a546bbd5b28e59b3298ee1eacc22c
tree
- 通过
git status
查看git仓库状态:
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: a.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
b.txt
然后我们就会发现,我们刚才的操作就类似 :
git add a.txt
修改文件和添加文件
修改文件
步骤如下:
- 修改 a.txt 内容:
goodbye world
- 提交 a.txt到git仓库:
$ git hash-object -w a.txt
c69ee2e20ca04f510508baa2f9b9ed3bc6032300
** .git/object **目录下有两个版本的a.txt :
- 更新暂存区,并且生产tree对象:
$ git update-index --add --cacheinfo 100644 c69ee2e20ca04f510508baa2f9b9ed3bc6032300 a.txt
$ git write-tree
f821a3927801b54d904f18dfd5eb0bdc96532abc
添加文件
把b文件当做新文件.
$ git hash-object -w b.txt
8f10d293354542a5babf0e66021e462a7ddb588d
$ git update-index --add --cacheinfo 100644 8f10d293354542a5babf0e66021e462a7ddb588d b.txt
$ git write-tree
1a6ba0f66390189a2c5c759bff5b6c77c4711818
查看当前状态
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: a.txt
new file: b.txt
Commit tree
现在我们有6d110a67b08a546bbd5b28e59b3298ee1eacc22c
、f821a3927801b54d904f18dfd5eb0bdc96532abc
、1a6ba0f66390189a2c5c759bff5b6c77c4711818
三个tree对象,我们分别查看包含的blob对象:
$ git cat-file -p 6d110a67b08a546bbd5b28e59b3298ee1eacc22c
100644 blob 8f10d293354542a5babf0e66021e462a7ddb588d a.txt
$ git cat-file -p f821a3927801b54d904f18dfd5eb0bdc96532abc
100644 blob c69ee2e20ca04f510508baa2f9b9ed3bc6032300 a.txt
$ git cat-file -p 1a6ba0f66390189a2c5c759bff5b6c77c4711818
100644 blob c69ee2e20ca04f510508baa2f9b9ed3bc6032300 a.txt
100644 blob 8f10d293354542a5babf0e66021e462a7ddb588d b.txt
即:
可以根据tree对象来访问不同版本的代码,但是这样我们要记住每一个tree对象的内容、何时创建、是谁保存等信息。如果能够给每一个tree对象附加说明信息,并且以链表的形式组织起来就好了。这时候我们就需要 commit tree:
- 通过
echo '说明信息' | git commit-tree tree对象SHA-1值
对6d110a67b08a546bbd5b28e59b3298ee1eacc22c
、来创建commit对象:
$ echo 'first commit' | git commit-tree 6d110a6
7b0860404899f161f92ba35fefff99a905389cc7
再对1a6ba0f66390189a2c5c759bff5b6c77c4711818
创建commit对象,并且通过参数-p 7b0860
指向 上一个commit-tree:
$ echo 'second commit' | git commit-tree 1a6ba0 -p 7b0860
7b0860404899f161f92ba35fefff99a905389cc7
- 然后查看两个tree对象的内容:
$ git cat-file -p 7b0860404899f161f92ba35fefff99a905389cc7
tree 6d110a67b08a546bbd5b28e59b3298ee1eacc22c
author unknown <1179373651@qq.com> 1602423688 +0800
committer unknown <1179373651@qq.com> 1602423688 +0800
first commit
$ git cat-file -p 7ab9a76432ce2ffa03a0f5a5a390ea990afc483f
tree 6d110a67b08a546bbd5b28e59b3298ee1eacc22c
parent 7b0860404899f161f92ba35fefff99a905389cc7
author unknown <1179373651@qq.com> 1602424538 +0800
committer unknown <1179373651@qq.com> 1602424538 +0800
second commit
即:
- 通过
git log 最后一个SHA-1值
查看一下提交记录:
$ git log --stat 7ab9a7
commit 7ab9a76432ce2ffa03a0f5a5a390ea990afc483f
Author: unknown <1179373651@qq.com>
Date: Sun Oct 11 21:58:02 2020 +0800
second commit
a.txt | 4 ++--
b.txt | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
commit 7b0860404899f161f92ba35fefff99a905389cc7
Author: unknown <1179373651@qq.com>
Date: Sun Oct 11 21:41:28 2020 +0800
first commit
a.txt | 2 ++
1 file changed, 2 insertions(+)
我们就可以根据 commit-tree
进行版本控制了。
(是不是很眼熟,这就是 commit的内部原理)。
Git场景4 模拟
- 我们在github创建一个项目 tryGit
- 通过
git clone
克隆远程仓库到本地
$ git clone git@github.com:loser-wang/tryGit.git
- 创建一个新分支,并且切换
$ git checkout -b work_branch
Switched to a new branch 'work_branch'
$ git branch
main
* work_branch
- 更改readme.txt
version1 !!
have a try
$ git add readme.md
$ git status
On branch work_branch
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: readme.md
$ git commit readme.md -m 'first version'
[work_branch c5bf091] first version
1 file changed, 1 insertion(+)
- 切换回 main主分支,并且合并:
$ git checkout main
Switched to branch 'main'
$ git merge work_branch
Updating b0f73f2..c5bf091
Fast-forward
readme.md | 1 +
1 file changed, 1 insertion(+)
- 在github上修改readme.md:
- 通过
git fetch
获取远程文件,并且通过git diff
比较不同
$ git fetch origin main
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 637 bytes | 21.00 KiB/s, done.
From github.com:loser-wang/tryGit
* branch main -> FETCH_HEAD
b0f73f2..916389e main -> origin/main
$ git diff main origin/main
diff --git a/readme.md b/readme.md
index 825b3ae..0210b0c 100644
--- a/readme.md
+++ b/readme.md
@@ -1,2 +1,2 @@
-version 1
+version 2
have a try!
$ git merge origin/main
Auto-merging readme.md
CONFLICT (content): Merge conflict in readme.md
Automatic merge failed; fix conflicts and then commit the result.
- 对两个版本的不同,进行取舍修改,然后合并,并且提交远程仓库:
$ git add readme.txt
$ git commit -m 'version 3'
[main 2d43083] version 3
$ git push origin main
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 517 bytes | 258.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:loser-wang/tryGit.git
916389e..2d43083 main -> main
- git rebase
$ git add .
$ git commit . -m 'version4'
[main ec1ccf6] version4
1 file changed, 2 insertions(+), 1 deletion(-)
$ git add .
$ git commit . -m 'version5'
[main cb932de] version5
1 file changed, 1 insertion(+), 1 deletion(-)
$ git rebase -i head^^
Successfully rebased and updated refs/heads/main.
参考: https://mp.weixin.qq.com/s/Km5KuXPETvG0wCGHrvj9Vg、《pro git》