一. git流程与回滚
1.1 Git操作说明

- commit 对当前一个目录文件进行操作的快照snapshot
- diff 两次commit之间的改变
- branch 对一次commit进行分开
- merge 两个分支进行合并
- 仓库 记录整个操作的一系列空间
Git与SVN的最大区别?
SVN存储的是diff,意味着当前commit时想要回到之前的某一次commit,只能一步步回退。Git存储了所有commit的快照,可以随时回退到指定commit。
1.2 Git工作流程


- workspace 工作目录
- Index Stage 索引区,即暂存区 git add把更改内容放入暂存区
- Repository 本地仓库,暂存区commit提交到本地仓库
- Remote 远程仓库,本地仓库内容push到远程仓库,本地仓库通过fetch/clone获取远程仓库内容,工作区通过checkout获取本地仓库内容
- pull 拉代码其实有两步操作,第一步fetch/clone到本地仓库,第二步checkout到工作区
1.3 Git操作
- 创建一个test文件夹,里面放入test.text文件内容如下
// test.text文件内容如下
----1----
----2----
----3----
----4----
----5----
// 第一步进入桌面test文件夹
$ cd /Users/wn/Desktop/test
// 初始化git
$ git init
// 检查git配置的 用户名 和 邮箱,默认是全局的配置
$ git config user.email
chris.wang@pingcoo.com
$ git config user.name
wang
// 更改用户名,只在当前工作目录里生效
$ git config user.name "Person"
$ git config user.name
Person
// 更改用户名,全局生效
$ git config --global user.name "Person"
// 把test.text文件放入暂存区
$ git add .
// 把test.text文件由暂存区提交到本地仓库
$ git commit -m "add 1-5"
[master 3a1ebc5] add 1-5
1 file changed, 0 insertions(+), 0 deletions(-)
rename "\346\265\213\350\257\225\346\226\207\344\273\266.text" => test.text (100%)
// 查看提交记录
$ git log
commit 3a1ebc5153d72d1bbd4139b65eeeaf3258a7c38c (HEAD -> master)
Author: Person <chris.wang@pingcoo.com>
Date: Sat Apr 24 17:06:59 2021 +0800
add 1-5
- 修改test.text内容如下
----1----
----2----
----3----
----4----
----5----
----6----
$ git add .
$ git commit -m "add 6"
[master ef34f5e] add 6
1 file changed, 1 insertion(+), 1 deletion(-)
这时已经有两次提交,这个时候如果我想回到上一次的提交,该怎么办?

-
git reset HEAD
这里实际上命令是git reset --mixd HEAD
默认--mixd只能操作没有提交的代码,也就是处于暂存区的代码 -
git reset --hard HEAD^
回退到当前分支的上一次提交 ,这种方式在MacOS shell下不支持 -
git rest --hard HEAD~1
使用这种方式,回退到当前分支的HEAD,HEAD就是当前分支最后一次提交
使用git reset
命令来进行回退
- 使用git reset --hard回退
$ git reset --hard HEAD~1
HEAD is now at 3a1ebc5 add 1-5
$ git log
commit 3a1ebc5153d72d1bbd4139b65eeeaf3258a7c38c (HEAD -> master)
Author: Person <chris.wang@pingcoo.com>
Date: Sat Apr 24 17:06:59 2021 +0800
add 1-5
// 此时查看test.text文件内容,成功回退到工作目录
----1----
----2----
----3----
----4----
----5----
- 如果使用git reset --soft回退
$ git reset --soft HEAD~1
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: test.text
// 此时查看test.text文件内容,成功回退到暂存区
----1----
----2----
----3----
----4----
----5----
----6----
使用git reset --soft回退到了暂存区,如果这个时候想再次回退到工作目录,该怎么办?

从上图可以看出git checkout 有两个作用,切换分支 和 重新存储工作区文件
// 先从暂存区移除
$ git reset HEAD .
Unstaged changes after reset:
M test.text
// 恢复工作目录的初始状态 --表示git checkout 后面不再接收参数,.(点)表示当前工作目录
$ git checkout -- .
// 此时查看test.text文件内容,成功回退到工作目录
----1----
----2----
----3----
----4----
----5----
下面验证.(点)表示当前工作目录?
重新修改test.text内容如下
----1----
----2----
----3----
----4----
----5----
----6----
$ git add .
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: test.text
// 成功从暂存区移除,与上面相同
$ git reset HEAD /Users/wn/Desktop/test
Unstaged changes after reset:
M test.text
二. git原理浅析
桌面创建Git原理
文件件,在Git原理
文件夹下面创建test文件夹
// 第一步进入桌面Git原理文件夹
$ cd /Users/wn/Desktop/Git原理
// 初始化git
$ git init
// 添加提交test文件夹
$ git add .
$ git commit -m "add test dir"
On branch master
Initial commit
nothing to commit
2.1 发现上面提交文件夹报了nothing to commit
,这是为什么呢?
git里面只有文件,并没有目录的概念。git是通过保存文件的路径找到对应的文件,这里提交的目录里面并没有文件,就报了nothing to commit
如果想提交目录该怎么办呢?
在目录里面创建以.keep .gitkeep为结尾的文件放入目录中就可以提交目录了
2.2 Git存储形式


- git根据本次提交修改的文件,计算文件的hash值作为key,修改的文件的压缩版本作为value,通过树的形式进行存储
- git hash-object 命令计算文件的hash值,再把文件的内容存入.git目录下
- git update-index 命令把生成的hash值放入暂存区,暂存区并没有保存文件的内容,只是保存的文件的hash值
- git write-tree 命令把暂存区中的内容生成树的形式存放起来
- git commit-tree
git commit
的命令把文件的hash值,文件的压缩版本存放在.git目录下
2.3 Git目录查看

在上面test文件夹里面分别创建 file_1.text
file_2.text
file_3.text
文件
// file_1.text内容
hello file_1
// file_2.text内容
hello file_2
// file_3.text内容
hello file_3
// 添加要提交的文件
$ git add .
添加之后,会把test目录下的文件的压缩版本放入.git目录下

现在查看压缩的value值跟文件的内容是否一致?
- git里面有一个命令专门用来查看二进制化的数据
// 成功打印出文件内容
$ git cat-file -p 4f91c8a57dbca2fdd5fbc253810326e362b58894
hello file_2
为什么要这么设计目录结构,而不直接使用40位hash作为文件名?
- 部分文件系统对目录下的文件数量有限制。例如,FAT32限制单目录下 的最大文件数量是65535个。
- 部分文件系统查找文件属于线性查找,目录下的文件越多,访问越慢。
- 通过这种目录形式查找,性能会提升很多
小结
Git对象存储,Git将存储对象的40位HASH分为两部分:
- A. 头两位作为文件夹
- B. 后38位作为对象文件名
.git/objects/hash[:2]/hash[2:40]
2.4 Git index文件
// 查看暂存区内容
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test/file_1.text
new file: test/file_2.text
new file: test/file_3.text

- index 表示存放在暂存区文件的一些信息
- Git在 .git 文件夹下面存放了 index 文件,该文件表示Git stage 的内
容。该文件是二进制文件,保存了被stage的文件的所有信息,像inode信息、 hash值等等
下面验证index文件存放的是暂存区文件的信息
$ hexdump /Users/wn/Desktop/Git原理/.git/index | grep '4f'
0000080 00 00 00 0d 4f 91 c8 a5 7d bc a2 fd d5 fb c2 53
// 可以看出来4f 91 c8 a5 7d bc a2 fd d5 fb c2 53刚好对应objects目录下的hash key值
下面删除index文件以及objects目录下生成的4f
54
82
目录,再来查看暂存区信息,发现暂存区没有了内容,提示test目录下有文件没有追踪,想把文件放入暂存区,这就意味着完成了git add .
命令的回退
$ git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
test/
nothing added to commit but untracked files present (use "git add" to track)
git add .
命令的作用
- 对test目录下的每个文件计算出hash值作为key值,把文件内容压缩作为value值,存放到.git目录下的objects目录
- 把git存放的一些信息放入暂存区,生成index文件
查看暂存区文件信息
// 先把文件重新添加回去
$ git add .
hello-world:Git原理 wangning$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test/file_1.text
new file: test/file_2.text
new file: test/file_3.text
$ git ls-files -s //查看暂存区文件信息
100644 82e15b6452f20ff614205ab96a12c4f566d275d0 0 test/file_1.text
100644 4f91c8a57dbca2fdd5fbc253810326e362b58894 0 test/file_2.text
100644 546701d3154b8c913b384e99bd55a6a94022dd13 0 test/file_3.text

- 100644 是8进制形式的,表示当前存储文件格式的信息,里面包含有rwx(读 写 执行)等信息
- 82e15b6452f20ff614205ab96a12c4f566d275d0 等信息,刚好与objects目录下新生成的目录一一对应
- test/file_1.text 表示test目录下的file_1.text文件
三. git操作原理
删除index文件以及objects目录下生成的4f
54
82
目录,重新恢复到git add .
之前的状态
3.1 计算文件的hash值
- 发现file_1.text每次计算的hash值都相同,原因是file_1.text文件内容没有更改
- 计算完file_1.text文件的hash值之后,发现objects目录下多了
82
目录

// 计算file_1.text文件的hash值
$ git hash-object -w ./test/file_1.text
82e15b6452f20ff614205ab96a12c4f566d275d0
// 修改file_1.text文件内容如下
hello file_1_1
// 重新就算file_1.text文件的hash值
$ git hash-object -w ./test/file_1.text
6cbaf697c05e3207e34613fd35d1b59e1576aa00
// 还原file_1.text文件内容如下
hello file_1
// 再次计算file_1.text文件的hash值
$ git hash-object -w ./test/file_1.text
82e15b6452f20ff614205ab96a12c4f566d275d0

对刚才的文件计算hash值操作,objects 目录下现在有 6c 82
两个目录
- 只有当文件内容不同时,才会重新计算hash值
- git不止存储了diff,还存储了所有操作的完整信息,有了完整信息就能快速回溯到某一次commit
// 查看`6c`目录下的提交内容
$ git cat-file -p 6cbaf697c05e3207e34613fd35d1b59e1576aa00
hello file_1_1
3.2 使用脚本计算文件的hash值
- objects目录下删除
6c 82
两个目录,使用脚本快速计算三个文件的hash值
$ for i in {1..3}; \
> do \
> echo "$(git hash-object -w ./test/file_${i}.text)" ;
> done
82e15b6452f20ff614205ab96a12c4f566d275d0
4f91c8a57dbca2fdd5fbc253810326e362b58894
546701d3154b8c913b384e99bd55a6a94022dd13
计算完hash值之后,objects目录下就会多出82 4f 54
三个目录

- 下面我们把test目录下的文件放入暂存区
// 把file_1.text文件放入暂存区
$ git update-index --add --cacheinfo 100644 82e15b6452f20ff614205ab96a12c4f566d275d0 test/file_1.text
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test/file_1.text
Untracked files:
(use "git add <file>..." to include in what will be committed)
test/file_2.text
test/file_3.text
上面已经把file_1.text
文件放入暂存区,还有file_2.text file_3.text
两个文件不在暂存区
- 使用脚本快速把test目录下的文件全部放入暂存区
$ for i in {1..3}; \
> do \
> git update-index --add --cacheinfo $(echo -n "100644 `git hash-object -w ./test/file_${i}.text` test/file_${i}.text" ) ;
> done
hello-world:Git原理 wangning$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test/file_1.text
new file: test/file_2.text
new file: test/file_3.text
这时已经把test目录下所有文件添加到暂存区中,成功模拟git add .
的两步操作
- 上面只是把文件放入暂存区,并没有按照树的形式去存储。接下来我们把暂存区的文件生成树,通过树的形式查找暂存区文件会更快
// 把test目录创建成一棵树,提交至暂存区,树0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c表示test目录
$ git write-tree
0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c

objects目录下多出了0a
目录,我们来查看下这个目录里面的内容

$ git cat-file -p 0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c
// 存放040000(目录的形式),类别是tree
040000 tree 3d4489e4d6909ba01b009d02fd9fdafc5fd5620d test
// hash值3d4489e4d6909ba01b009d02fd9fdafc5fd5620d表示test目录下的三个文件
$ git cat-file -p 3d4489e4d6909ba01b009d02fd9fdafc5fd5620d
100644 blob 82e15b6452f20ff614205ab96a12c4f566d275d0 file_1.text
100644 blob 4f91c8a57dbca2fdd5fbc253810326e362b58894 file_2.text
100644 blob 546701d3154b8c913b384e99bd55a6a94022dd13 file_3.text
hash值3d4489e4d6909ba01b009d02fd9fdafc5fd5620d表示test目录,是不是把test目录也添加到git目录了?
- test目录在.git目录下是以树的形式存在的
- 以树的形式存储,查找更快更便捷

// 去git中重新创建一个目录,指定我们的子树
// 把上面创建的树0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c作为一棵子树,--prefix=test1表示重新生成的目录为test1,0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c表示子树的hash值
$ git read-tree --prefix=test1/ 0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c
// 重新创建一棵树,提交至暂存区
$ git write-tree
bba4a65ddc0dd0814a9dc1de2c91f86f664c4562
// 查看到暂存区有两棵树,分别是test 与 test1
// 树test是通过git add .生成的
// 树test1是我们自己构建的树,树test1里面包含有一棵子树test
$ git cat-file -p bba4a65ddc0dd0814a9dc1de2c91f86f664c4562
040000 tree 3d4489e4d6909ba01b009d02fd9fdafc5fd5620d test
040000 tree 0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c test1
// new file: test1/test/file_1.text ... 发现多了三个文件至暂存区
// deleted: test1/test/file_1.text 表示file_1.text虽然提交至暂存区,但是并不在工作目录里
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test/file_1.text
new file: test/file_2.text
new file: test/file_3.text
new file: test1/test/file_1.text
new file: test1/test/file_2.text
new file: test1/test/file_3.text
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: test1/test/file_1.text
deleted: test1/test/file_2.text
deleted: test1/test/file_3.text
// 恢复暂存区里面的test1目录
$ git checkout -- test1

小结
- 这里相当于把树test1恢复了
- git里面目录只代表路径,并不是真实存在
3.3 探索git哪步命令会生成树
删除.git/objects目录下生成的hash目录,以及.git目录下的index文件,恢复到初始状态
// 添加要提交的文件至暂存区
$ git add .

通过上图我们发现ojbects目录下并没有生成树的hash文件,得出结论git add .
并不会生成树
// 我们再来提交test目录下的文件
$ git commit -m 'add test dir'
[master (root-commit) 0764060] add test dir
3 files changed, 3 insertions(+)
create mode 100644 test/file_1.text
create mode 100644 test/file_2.text
create mode 100644 test/file_3.text
$ git log --oneline --decorate --graph --stat
* 0764060 (HEAD -> master) add test dir
test/file_1.text | 1 +
test/file_2.text | 1 +
test/file_3.text | 1 +
3 files changed, 3 insertions(+)

通过上图得出结论git commit -m
会生成树
// 查看当前暂存区内容
$ git ls-files -s
100644 82e15b6452f20ff614205ab96a12c4f566d275d0 0 test/file_1.text
100644 4f91c8a57dbca2fdd5fbc253810326e362b58894 0 test/file_2.text
100644 546701d3154b8c913b384e99bd55a6a94022dd13 0 test/file_3.text
$ git write-tree
0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c
// 每生成一棵树,objects目录下就会多出对应的目录
$ echo 'init commit' | git commit-tree 0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c
adac26a870c19dc1adc89fe9f755b521dd46a4b5
// 查看到整个的提交信息作者以及邮箱,这里生成了一个commit
$ git cat-file -p adac26a870c19dc1adc89fe9f755b521dd46a4b5
tree 0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c
author wn <chris.wang@pingcoo.com> 1619532018 +0800
committer wn <chris.wang@pingcoo.com> 1619532018 +0800
init commit
// 查看到整个的log信息
$ git log --oneline --decorate --graph --stat
* 0764060 (HEAD -> master) add test dir
test/file_1.text | 1 +
test/file_2.text | 1 +
test/file_3.text | 1 +
3 files changed, 3 insertions(+)
// 查看commit 树的提交信息
$ git log --oneline --decorate --graph --stat adac26a870c19dc1adc89fe9f755b521dd46a4b5
* adac26a init commit
test/file_1.text | 1 +
test/file_2.text | 1 +
test/file_3.text | 1 +
3 files changed, 3 insertions(+)
//
$ echo 'second commit' | git commit-tree 0a4f9e18c5ebbe28b1220cbf614d4bac541b2a6c -p adac26a870c19dc1adc89fe9f755b521dd46a4b5
98fe954fd81c0934fb7471d28029e5d0665091df
// 可以看出有两次提交,第一次提交有确切的值,第二次提交的是空
$ git log --oneline --decorate --graph --stat 98fe954fd81c0934fb7471d28029e5d0665091df
* 98fe954 second commit
* adac26a init commit
test/file_1.text | 1 +
test/file_2.text | 1 +
test/file_3.text | 1 +
3 files changed, 3 insertions(+)
- commit-tree 保存的是每次提交的hash值,并不是保存的真正的提交信息
- git通过保存的hash值,找到提交的信息

分支保存最后一次提交的hash值,通过hash值在树里面查找,回溯到分支的根结点
网友评论