Git(一)

作者: 浅墨入画 | 来源:发表于2021-04-27 22:47 被阅读0次

一. git流程与回滚

1.1 Git操作说明
image.png
  • commit 对当前一个目录文件进行操作的快照snapshot
  • diff 两次commit之间的改变
  • branch 对一次commit进行分开
  • merge 两个分支进行合并
  • 仓库 记录整个操作的一系列空间

Git与SVN的最大区别?
SVN存储的是diff,意味着当前commit时想要回到之前的某一次commit,只能一步步回退。Git存储了所有commit的快照,可以随时回退到指定commit。

1.2 Git工作流程
image.png image.png
  • 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(-)
这时已经有两次提交,这个时候如果我想回到上一次的提交,该怎么办?
image.png
  • 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回退到了暂存区,如果这个时候想再次回退到工作目录,该怎么办?
image.png

从上图可以看出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存储形式
image.png image.png
  • 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目录查看
image.png

在上面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目录下

image.png
现在查看压缩的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
image.png
  • 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
image.png
  • 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目录
image.png
// 计算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
image.png

对刚才的文件计算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三个目录

image.png
  • 下面我们把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
image.png

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

image.png
$ 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目录下是以树的形式存在的
  • 以树的形式存储,查找更快更便捷
image.png
// 去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目录
小结
  • 这里相当于把树test1恢复了
  • git里面目录只代表路径,并不是真实存在
3.3 探索git哪步命令会生成树

删除.git/objects目录下生成的hash目录,以及.git目录下的index文件,恢复到初始状态

// 添加要提交的文件至暂存区
$ git add .
image.png

通过上图我们发现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(+)
image.png

通过上图得出结论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值,找到提交的信息
image.png

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

相关文章

网友评论

      本文标题:Git(一)

      本文链接:https://www.haomeiwen.com/subject/uxmkrltx.html