探究Git基本原理(下)

电子说

1.2w人已加入

描述

加深理解 commit 提交

执行完成了 git commit 命令,究竟做了什么呢?

当我们再次对 file2.txt 文件的内容进行变更、添加以及提交之后,发现在提交的时候,查看的 commit 对象的内容时,其包含有父节点的 commit 信息。而对于理解的话,可以看看下面的这个提交流程图。

# 左边执行
$ echo "file2.txt" > file2.txt
$ git status
$ git add file2.txt
$ git ls-files -s
$ git cat-file -p 0ac9638
$ git commit -m "2nd commit"
$ git cat-file -p bab53ff
$ git cat-file -p 2f07720

# 右边执行
$ watch -n 1 -d tree .git

Git

Git

在 Git 中空文件夹是不算在追踪范围内的,而且添加文件夹并不会增加 object 对象。当我们查看 index 内容的时候,会发现文件名称是包含相对路径的。

而当我们通过 commit 命令提交之后,会发现生成了三个 object 对象,因为 commit 操作不会生成 blob 对象,所以分别是一个 commit 对象和两个 tree 对象。可以发现,tree 对象里面有包含了一个目录的 tree,其里面包含对象文件内容。

下图所示的文件状态,可以体会到 Git 中版本的概念。即 commit 对象指向一个该版本中的文件目录树的根(tree),然后 tree 在指向 blob 对象(文件)和 tree 对象(目录),这样就可以无限的往复下去形成一个完整的版本。

# 左边执行
$ mkdir floder1
$ echo "file3" > floder1/file3.txt
$ git add floder1
$ git ls-files -s
$ git commit -m "3rd commit"
$ git cat-file -p 1711e01
$ git cat-file -p 9ab67f8

# 右边执行
$ watch -n 1 -d tree .git

Git

文件的生命周期状态

总结一下,Git 里面的文件状态和如何切换。

现在,我们已经基本理解了文件如何在工作区、暂存区以及代码仓库之间进行状态的跟踪和同步。在 Git 的操作中,文件的可能状态有哪些,以及如何进行状态切换的,我们这里一起总结一下!

Git

Git

Branch 和 HEAD 的意义

执行完成了 git branch 命令,究竟做了什么呢?

到底什么是分支?分支切换又是怎么一回事?我们通过查看 Git 的官方文档,就可以得到,分支就是一个有名字的(master/dev)指向 commit 对象的一个指针。

我们在初始化仓库的时候,提供会默认给我们分配一个叫做 master 的分支(在最新的版本默认仓库已经变更为 main 了),而 master 分支就是指向最新的一次提交。为什么需要给分支起名字呢?就是为了方便我们使用和记忆,可以简单理解为 alias 命令的意义一致。

Git

有了上述基础,我们就需要考虑下,分支到底是如何实现和工作的。要实现一个分支,我们最基本需要解决两个问题,第一个就是需要存储每一个分支指向的 commit,第二个问题就是在切换分支的时候帮助我们标识当前分支。

在 Git 中,它有一个非常特殊的 HEAD 文件。而 HEAD 文件是一个指针,其有一个特性就是总会指向当前分支的最新的一个 commit 对象。而这个 HEAD 文件正好,解决了我们上面提出的两个问题。

当我们从 master 切换分支到 dev 的时候,HEAD 文件也会随即切换,即指向 dev 这个指针。设计就是这么美丽,不愧是鬼才,好脑袋。

Git

# 左边执行
$ cat .git/HEAD
$ cat .git/refs/heads/master
$ git cat-file -t 1711e01

# 右边执行
$ glo = git log

Git

分支操作的背后逻辑

执行完成了 git branch 命令,究竟做了什么呢?

这里我们可以看到分支切换之后,HEAD 指向发生变动了。

# 左边执行
$ git branch
$ git branch dev
$ ll .git/refs/heads
$ cat .git/refs/heads/master
$ cat .git/refs/heads/dev
$ cat .git/HEAD
$ git checkout dev
$ cat .git/HEAD

# 右边执行
$ glo = git log

Git

这里需要注意的是,即使我们删除了分支,但是该分支上一些特有的对象并不会被删除的。这些对象其实就是我们俗称的垃圾对象,还有我们多次使用 add 命令所产生的也有垃圾对象,而这些垃圾对象怎么清除和回收呢?后续,我们会涉及到的。

# 左边执行
$ echo "dev" > dev.txt
$ git add dev.txt
$ git commit -m "1st commit from dev branch"
$ git checkout master
$ git branch -d dev
$ git branch -D dev
$ git cat-file -t 861832c
$ git cat-file -p 861832c
$ git cat-file -p 680f6e9
$ git cat-file -p 38f8e88

# 右边执行
$ glo = git log

Git

checkout 和 commit 操作

我们一起聊一聊,checkout 和 commit 的操作!

我们执行 checkout 命令的时候,其不光可以切换分支,而且可以切换到指定的 commit 上面,即 HEAD 文件会指向某个 commit 对象。在 Git 里面,将 HEAD 文件没有指向 master 的这个现象称之为 detached HEAD。

这里不管 HEAD 文件指向的是分支名称也好,是 commit 对象也罢,其实本质都是一样的,因为分支名称也是指向某个 commit 对象的。

Git

# 左边执行
$ git checkout 6e4a700
$ git log

# 右边执行
$ glo = git log

Git

当我们切换到指定的 commit 的时候,如果需要在对应的 commit 上继续修改代码提交的话,可以使用上述图片中提及的 swtich 命令创建新分支,再进行提交。但是,通常我们都不会着玩,都会使用 checkout 命令来创建新分支的。

$ git checkout -b tmp
$ git log

即使可以这样操作,我们也很少使用。还记得我们上一章节创建的 dev 分支吗?我们创建了该分支并有了一个新的提交,但是没有合并到 master 分支就直接删除了。现在再使用 log 命令查看的话,是看不到了。

实际,真的看不到了吗?大家要记住,在 Git 里面任何的操作,比如分支的删除。它只是删除了指向某个特定 commit 的指针引用而已,而那个 commit 本身并不会被删除,即 dev 分支的那个 commit 提交还是在的。

那我们怎么找到这个 commit 呢?找到之后,我们就可以在上面继续工作,或者找到之前的文件数据等。

第一种方法:

  • [费劲不太好,下下策]
  • 在 objects 目录下面,自己一个一个看,然后切换过去。

第二种方法:

  • [推荐的操作方式]
  • 使用 Git 提供的 git reflog 专用命令来查找。
  • 该命令的作用就是用于将我们之前的所有操作都记录下来。

# 左边执行
$ git reflog
$ git checkout 9fb7a14
$ git checkout -b dev

# 右边执行
$ glo = git log

Git

Git

聊聊 diff 的执行逻辑

当我们执行 diff 命令之后,Git 的逻辑它们是怎么对比出来的呢?

就在本节中中,我们使用上节的仓库,修改文件内容之后,看看 diff 命令都输出了哪些内容呢?我们这里一起来看看,研究研究!

$ echo "hello" > file1.txt
$ git diff
$ git cat-file -p 42d9955
$ git cat-file -p ce01362

# 下述命令原理也是一样的
$ git diff --cached
$ git diff HEAD

Git

Git 如何添加远程仓库

如何将我们本地的仓库和远程服务器上面的仓库关联起来呢?

初始化仓库

$ git init
$ git add README.md
$ git commit -m "first commit"

关联远程仓库

当我们使用上述命令来关联远程服务器仓库的时候,我们本地 .git 目录也是会发生改变的。通过命令查看 .git/config 文件的话,可以看到配置文件中出现了 [remote] 字段。

# 关联远程仓库
$ git remote add origin git@github.com:escapelife/git-demo.git

➜ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true

[remote "origin"]
url = git@github.com:escapelife/git-demo.git
fetch = +refs/heads/*:refs/remotes/origin/*

推送本地分支

当我们执行如下命令,将本地 master 分支推送到远程 origin 仓库的 master 分支。之后,我们登陆 GitHub 就可以看到推送的文件及目录内容了。

推送分支内容的时候,会列举推送的 objects 数量,并将其内容进行压缩,之后推送到我们远程的 GitHub 仓库,并且创建了一个远程的 master 分支(origin 仓库)。

# 推送本地分支
$ git push -u origin master

推送之后,我们可以发现,本地的 .git 生成了一些文件和目录,它们都是什么呢?如下所示,会新增四个目录和两个文件,皆为远程仓库的信息。当我们通过命令查看 master 这个文件的内容时,会发现其也是一个 commit 对象。此时与我们本地 master 分支所指向的一致。而其用于表示远程仓库的当前版本,用于和本地进行区别和校对的。

➜ tree .git
├── logs
│ ├── HEAD
│ └── refs
│ ├── heads
│ │ ├── dev
│ │ ├── master
│ │ └── tmp
│ └── remotes # 新增目录
│ └── origin # 新增目录
│ └── master # 新增文件
└── refs
├── heads
│ ├── dev
│ ├── master
│ └── tmp
├── remotes # 新增目录
│ └── origin # 新增目录
│ └── master # 新增文件
└── tags

远程仓库存储代码

使用 GitLab 来了解远程仓库的服务器到底是如何存储,我们的代码的!

当我们编写完代码之后,将其提交到对应的远程服务器上面,其存储结构和我们地址是一模一样的。如果我们仔细想想的话,不一样的话才见怪了。

Git 本来就是代码的分发平台,无中心节点,即每个节点都是主节点,所以其存储的目录结构都是一直的。这样,不管哪一个节点的内容发生丢失或缺失的话,我们都可以通过其他节点来找到。而 Git 服务器就是一个可以帮助我们,实时都可以找到的节点而已。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

file2.txt$ git status$ ">
×
20
完善资料,
赚取积分