前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >git底层原理,从常见操作解释git的底层原理,再也不怯

git底层原理,从常见操作解释git的底层原理,再也不怯

作者头像
公众号 IT老哥
发布2021-01-12 14:49:55
2.2K0
发布2021-01-12 14:49:55
举报

一、git的基本使用

在这里插入图片描述

  1. git clone 克隆远程资源到本地目录,作为工作目录;
  2. 然后在本地的克隆目录上添加或修改文件;
  3. 如果远程修改了,需要同步远程的内容,直接git pull就可以更新本地的文件;
  4. 本地在修改之后,可以通过git status 查看修改的文件。然后使用git add添加修改的文件暂到缓冲区;
  5. 在添加之后,可以使用git commit添加到当前的工作区;
  6. 在修改完成后,如果发现错误,可以撤回提交并再次修改并提交;
  7. git push将本地的修改推送到远程的git服务器。

二、版本管理模式

每个开发者拥有自己仓库的写权限和其他所有人仓库的读权限。这种情形下通常会有个代表“官方”项目的权威的仓库。

要为这个项目做贡献,你需要从该项目克隆出一个自己的公开仓库,然后将自己的修改推送上去。

接着你可以请求官方仓库的维护者拉取更新合并到主项目。

维护者可以将你的仓库作为远程仓库添加进来,在本地测试你的变更,将其合并入他们的分支并推送回官方仓库。

在这里插入图片描述

  1. 项目维护者推送到主仓库。
  2. 贡献者克隆此仓库,做出修改。
  3. 贡献者将数据推送到自己的公开仓库。
  4. 贡献者给维护者发送邮件,请求拉取自己的更新。
  5. 维护者在自己本地的仓库中,将贡献者的仓库加为远程仓库并合并修改。
  6. 维护者将合并后的修改推送到主仓库。

!! 当然你也可以像使用SVN一样使用一个中心仓库来交换信息(在项目人员较少时常用这种方式),但是这种工作方式和SVN也不尽全部相同,它们的区别在于,使用git进行开发的人员,每一个节点(每一个电脑上的本地仓库)都拥有完整的仓库,在失去与中心仓库的连接时也可以工作(先提交到本地仓库)。

三、存储实现原理(Git对象)

在git中以存储键值对(key-value)的方式来存储文件。

它允许插入任意类型的内容,并会返回一个键值,通过该键值可以在任何时候再取出该内容。

git的kv中value一般有以下几种类型blobtreecommit

1. git add原理

在调用git add后会生成一个blob对象,然后将该对象add放进进index区。

(1)生成Blob对象

首先,我们需要初始化一个新的 Git 版本库

代码语言:javascript
复制
$ git init
Initialized empty Git repository in D:/GitTest/.git/

然后我们查看一下生成的目录结构

代码语言:javascript
复制
$ find .git/ -type d # 查看.git/下的所有目录
.git/
.git/hooks
.git/info
.git/objects
.git/objects/info
.git/objects/pack
.git/refs
.git/refs/heads
.git/refs/tags

git中所有生成的对象都存储在objects中,因为我们研究git存储原理,所以下来着重观察这一目录。

代码语言:javascript
复制
$ find .git/objects/ -type f # 查看.git/objects下的所有文件

我们发现这个目录下是空的,可以通过底层命令 git hash-object 将任意数据保存于.git/objects 目录(即对象数据库),并返回指向该数据对象的唯一的键(kv)。

代码语言:javascript
复制
$ echo 'first add file'>readme.md

$ git hash-object -w readme.md
warning: LF will be replaced by CRLF in readme.md.
The file will have its original line endings in your working directory.
6a0b867bdc470c582c15906f264b9fec371cdbfc
## 这警告是因为换行符在不同系统中格式不一样,为了分布式开发,会自适应系统转换

git hash-object 会接受你传给它的东西,而它只会返回可以存储在 Git 仓库中的唯一键。

-w 选项会指示该命令不要只返回键,还要将该对象写入数据库中。此命令输出一个长度为 40 个字符的校验和。

这是一个 SHA-1 哈希值——一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。

我们再次查看objects目录

代码语言:javascript
复制
$ find .git/objects/ -type f
.git/objects/6a/0b867bdc470c582c15906f264b9fec371cdbfc

这就是开始时 Git 存储内容的方式——一个文件对应一条内容, 以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。

校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。可以看到它的hash值就是我们之前生成的值。

值得一提的是只要内容没变那么hash值也不会变,也就是说不会生成新的对象。

我们可以使用giit cat-file查看该目录下的对象,-t选项表示查看对象类型,-p表示查看对象内容。

代码语言:javascript
复制
$ git cat-file -t 6a0b867bdc470c5 #可以只使用部分前缀,只要确能找到唯一对象即可。
blob
$ git cat-file -p 6a0b867bdc470c5
first add file
(2)将修改添加到暂存区(index区)

我们使用git status可以查看index区的状态,可以很明显的看到我们的文件是红色的,表示文件目前未被加入index区。

我们接下来调用git update-index命令将该文件加入index区,因为第一次添加该文件我们要使用--add选项

代码语言:javascript
复制
$ git update-index --add readme.md

我们再次使用git status查看index区状态,发现readme.md已经变成绿色,表示我们已经成功添加到了index区。

当然我们可以直接调用git update-index \--add readme.md,而不需要生成手动生成blob对象,但是它的底层原理就是会生成一个blob对象,我们这里只是为了演示这个过程,让大家方便理解。

2.git commit原理

进行代码提交时,需要根据暂存区的内容,先生成tree对象,再生成commit对象,然后会将记录记录到logs文件夹下。

(1)生成tree对象

可以通过 git write-tree命令将暂存区内容写入一个树对象。

代码语言:javascript
复制
$ git write-tree
d8d965c56c04e851e3b47f524c6c52a24c396857

那么tree对象到底是什么呢,我们使用git cat-file查看该内容

代码语言:javascript
复制
$ git cat-file -p d8d965c56c0
100644 blob 6a0b867bdc470c582c15906f264b9fec371cdbfc    readme.md

100644 表示该文件类型为文本文件。

blob 表示该对象类型。

6a0b867bdc470c582c15906f264b9fec371cdbfc 表示该文件的 hash值。

可以看到和之前我们写入时的值是一样的。

我们添加一个目录,然后将他加入index区,再生成新的tree对象,来查看目录结构再tree中如何表示。

代码语言:javascript
复制
$ mkdir -p com/git #递归生成目录
$ echo 'add new file and dirs'>com/git/test.txt
$ git add -A #将工作区所有改变添加到index区
$ git write-tree
956902378ddcc3a768459a852253d1c69bb3a21e
$ git cat-file -p 956902378ddcc3a76
040000 tree 108f37e7bececc2fd72e90d34e9e75ef49b65265    com
100644 blob 6a0b867bdc470c582c15906f264b9fec371cdbfc    readme.md

我们可以发现目录的文件类型是040000 ,他在objects中用tree对象表示,那么结构已经很明显了。

如果我们在去查看comtree对象会发现里面存储了javatree对象,再去查看会发现test.txt的blob对象。

(2)生成commit对象

可以通过调用 commit-tree命令创建一个commit对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话)。

我们从之前创建的第一个树对象开始:

代码语言:javascript
复制
$ echo 'first commit' | git commit-tree d8d965c56c04e8
f742a0cde276d89f79505a500071b6e2577dda45

由于创建时间和作者数据不同,你会得到一个不同的散列值。我们查看commit对象

代码语言:javascript
复制
$ git cat-file -p f742a0cde276d
tree d8d965c56c04e851e3b47f524c6c52a24c396857
author jyx <xxxxxx@163.com> 1586695870 +0800
committer jyx <xxxx@163.com> 1586695870 +0800

提交对象的格式很简单:它先指定一个顶层树对象,代表此次提交点的项目快照;然后是可能存在的父提交(即上一次提交,前面描述的提交对象并不存在任何父提交);

之后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳);

留空一行,最后是提交注释。

接着,我们将创建另一个提交对象,它们引用上一个提交(作为其父提交对象):

代码语言:javascript
复制
$ echo 'second commit' | git commit-tree 956902378ddcc3a7 -p 9fc68968003d
3d87a34c5c890d15a3c9eacf935d65085621694d

我们使用git log <commitid>就可以看到一个提交历史了。

代码语言:javascript
复制
$ git log --stat 3d87a34c5

在这里插入图片描述

那么一个commit对象可以用下图表示

在这里插入图片描述

3.git branch/git tag原理

(1)创建分支/标签

我们所有的对象都已经介绍完毕了,那么branchtag是如何实现的呢,聪明的你是否已经想到,没错就是使用指针

因为我们的项目目前还没有任何分支,所以首先我们基于提交点创造一个分支。(在新项目第一次提交时会默认创建一个master分支)

代码语言:javascript
复制
## 基于commit对象创建分支
$ git branch master 3d87a34c5c890d15a3c9eacf935d65085621694d
## 基于分支创建分支
$ git branch dev master
## 基于分支创建tag
$ git tag tag_from_master master
## 基于tag创建分支
$ git branch branch_from_tag tag_from_master
## 等等

使用git branch查看分支信息

代码语言:javascript
复制
$ git branch
  branch_from_tag
  dev
* master

使用git tag查看标签

代码语言:javascript
复制
$ git tag
tag_from_master
(2)实现原理

创建分支后,我们发现.git/refs/heads/下多了一些文件

在这里插入图片描述

可以看到这些文件就是我们之前创建的三个分支名称,我们查看这三个文件内容。

在这里插入图片描述

我们在查看tag文件,tag文件存储在.git/refs/tags/

在这里插入图片描述

至此我们明白了,所谓的branchtag都是基于commit对象生成的,他们指向了某一个提交对象。

4.Head指针

每一个分支上都一个head指针,表示当前分支的最新提交点。

我们的.git文件夹下有个HEAD文件。我们打开它查看内容,然后使用git checkout切换分支,在查看内容

在这里插入图片描述

我们发现了分支的切换实际是改变总的HEAD指针指向.git/refs/heads下的某个分支文件。

我们在使用git reset时,实际上有一步操作就是将HEAD指针指向我们要回滚的提交点(可以是tag/branch/commit,实际上都是commit对象)。

我们在知道了这些原理后即使我们有时失误使用--hard进行回滚,我们还可以找到以前的commit对象,来让版本在恢复到回滚之前。

四、总结

在这里插入图片描述

在这个图中,我们可以看到部分 Git 命令是如何影响工作区和暂存区(stage, index)的。

  1. 图中左侧为工作区,右侧为版本库。在版本库中标记为 “index” 的区域是暂存区(stage, index),标记为 master 的是master 分支所代表的目录树。
  2. 图中我们可以看出此时 HEAD实际是指向 master 分支的一个指针。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
  3. 图中的 objects标识的区域为 Git 的对象库,实际位于 .git/objects"目录下。
  4. 当对工作区修改(或新增)的文件执行 “git add” 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID 被记录在暂存区的文件索引中。
  5. 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
  6. 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
  7. 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
  8. 当执行 git checkout 或者git checkout -- <file>命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。
  9. 当执行 git checkout HEAD或者 git checkout HEAD <file>命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改 动。

!! 了解了git的底层原理那么在使用git时就会更加顺手,把他当成一个服务于你的工具。本文参考于 git官方文档。

源于:blog.csdn.net/weixin_42762133/article/details/105458252

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT老哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、git的基本使用
  • 二、版本管理模式
  • 三、存储实现原理(Git对象)
    • 1. git add原理
      • (1)生成Blob对象
      • (2)将修改添加到暂存区(index区)
    • 2.git commit原理
      • (1)生成tree对象
      • (2)生成commit对象
    • 3.git branch/git tag原理
      • (1)创建分支/标签
      • (2)实现原理
    • 4.Head指针
    • 四、总结
    相关产品与服务
    文件存储
    文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档