git内部原理及常用操作指令学习

前言

git是每一个程序员必须熟练使用的一个工具,但是在当前这个浮躁的社会,特别是正在大发展的前端领域,大家似乎只是在乎怎么使用,而并不想去了解内部的实现原理,虽然对于大部分的第三方包来说我们确实不用去在意它的实现细节,但是对于git这个陪伴我们无数日日夜夜的工具来说还是有必要揭开她的外衣一探究竟的。

本文将以一个空项目开始的方式一步一步去探索git的内部实现逻辑。

重点概念

SHA-1

SHA-1 是一种不可逆的加密算法,SHA-1可以将不固定大小的数据生成为一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。

Git分区

Workspace:工作区
Index / Stage:暂存区
Repository:本地仓库
Remote:远程仓库

Git Objects对象类型

  1. blob:保存了提交文件的数据快照
  2. tree:保存了本次提交的所有文件的blob对象信息
  3. commit:保存了本次提交的信息和tree对象信息

以上三种是比较重要的对象类型

目录结构

我了解一个项目都是从目录结构开始的,所以学习git我们也是从它的目录结构入手

#新建空项目并初始化
$ git init

#进入.git文件可以看到目录结构
│  config #存放一些仓库的配置信息
│  description #存放仓库的描述信息,主要给git托管系统使用
│  HEAD #本地映射到refs文件的引用,能够找到下一次commit的前一次哈希值(远程分支为ORIG_HEAD)
│  
├─hooks #存放一些shell脚本
│
├─info #存放一些仓库信息
│      exclude
│
├─objects #存放git的各种类型object
│  ├─info
│  └─pack
└─refs #保存引用的相关信息
    ├─heads
    └─tags


这里重点要关注的是 HEAD、objects、refs。

GIT储存原理

1. 文件快照储存

新建test.txt并写入内容 hello git!,执行git add .,可以发现objects文件夹发生了变化:

objects:.
├─27
│      706f8151e4c44bb7a129d64b35fff3422d5e3a #新增文件
│
├─info
└─pack

27 和 706f8151e4c44bb7a129d64b35fff3422d5e3a拼接在一起刚好是40位,这个就是根据你的文件由SHA1算法生成的40位16进制哈希值,而这个文件就是用来保存你的文件数据快照,你的文件有多大,它就有多大。

这个文件是二进制文件,无法直接查看,但可以通过git提供的命令进行查看:

$ git cat-file -t 27706f8151e4c44bb7a129d64b35fff3422d5e3a #查看object类型
blob

$ git cat-file -p 27706f8151e4c44bb7a129d64b35fff3422d5e3a #查看object数据
hello git!

这时我们还会发现根目录下也新增了一个index文件,也是二进制文件,这个是管理暂存区的文件,同样我们可以使用git提供的命令进行查看:

$ git ls-files --stage #查看index文件
100644 27706f8151e4c44bb7a129d64b35fff3422d5e3a 0       test.txt

这个文件保存了我们暂存文件的blob对象的哈希值,也可以理解为保存了一个指向这个对象的指针。

$ git reset HEAD -- . #取消暂存区文件

$ git ls-files --stage #查看index文件发现没有任何输出值

2. 提交信息管理

$ git commit -m '首次提交'
[master (root-commit) e895c99] 首次提交
 1 files changed, 1 insertions(+)
 create mode 100644 test.txt

objects文件夹发生了变化:

objects:.
├─27
│      706f8151e4c44bb7a129d64b35fff3422d5e3a
│
├─98
│      b241e3ee5f307af72f5aaafb154dbfb54c3a30 #新增文件
│
├─e8
│      95c99416c3adf72869db580b4c729891f27d0d #新增文件
│
├─info
└─pack

新增了两个哈希命名的文件,老规矩,我们先查看一下

$ git cat-file -t 98b241e3ee5f307af72f5aaafb154dbfb54c3a30
tree #tree类型object

$ git cat-file -p 98b241e3ee5f307af72f5aaafb154dbfb54c3a30
100644 blob 27706f8151e4c44bb7a129d64b35fff3422d5e3a    test.txt

#tree对象保存了本次提交的相关文件快照对象的信息

$ git cat-file -t e895c99416c3adf72869db580b4c729891f27d0d
commit #commit类型object

$ git cat-file -p e895c99416c3adf72869db580b4c729891f27d0d
tree 98b241e3ee5f307af72f5aaafb154dbfb54c3a30
author username <xxx@qq.com> 1606826893 +0800
committer username <xxx@qq.com> 1606826893 +0800

首次提交

#commit对象保存了本次提交的作者、提交者、提交时间、提交附带信息以及最关键的tree对象的信息

这里我们已经发现了commit -> tree -> blob的关系,那谁指向commit呢?

这时我们可以发现refs文件夹也发生了变化,heads文件夹下多了个master文件,内容为本次更新的哈希值:

e895c99416c3adf72869db580b4c729891f27d0d

而根目录下的HEAD文件内容又为:

ref: refs/heads/master

这样我们就建立了从HEAD -> master -> commit -> tree -> blob的完整关系链。

而根目录下新增的logs文件夹,跟踪了各个分支上的所有操作,其实从里面的文件我们可以更加直观的看到我们进行的一些git操作

logs:.
│  HEAD
│
└─refs
    └─heads
            master

#此时 HEAD 和 master 文件保存了同样的信息
0000000000000000000000000000000000000000 e895c99416c3adf72869db580b4c729891f27d0d username <xxx@qq.com> 1606826893 +0800    commit (initial): 首次提交
#前两个哈希值表示前一次提交和本次提交的commit对象哈希值,后面是用户名,用户邮箱,提交时间,提交附加内容

接下来我们对test.txt进行二次修改并执行git add .git commit操作

#logs下的HEAD文件很明显的记录了我们的操作
0000000000000000000000000000000000000000 e895c99416c3adf72869db580b4c729891f27d0d username <xxx@qq.com> 1606826893 +0800    commit (initial): 首次提交
e895c99416c3adf72869db580b4c729891f27d0d 8f15afaae122a0eeaabc04fb1dc3ab36e3ecbb90 username <xxx@qq.com> 1606828104 +0800    commit: 第二次提交

我们知道8f15afaae122a0eeaabc04fb1dc3ab36e3ecbb90是本次的commit类型对象

#查看commit对象内容
$ git cat-file -p 8f15afaae122a0eeaabc04fb1dc3ab36e3ecbb90
tree 910e967c436b0824e4ac0aebd4963c64bdd5f31b
parent e895c99416c3adf72869db580b4c729891f27d0d
author username <xxx@qq.com> 1606828104 +0800
committer username <xxx@qq.com> 1606828104 +0800

第二次提交

可以看到这次的commit对象不仅仅记录了本次的tree对象,也记录了上次的commit对象,这样也就保持了与上次commit的所有信息的联系,以此类推,多少次的提交我们也都可以像链表一样将它们联系起来。

#查看tree对象内容
$ git cat-file -p 910e967c436b0824e4ac0aebd4963c64bdd5f31b
100644 blob cb7a44021ad5013a4620857a2d67f4db9ca2bccb    test.txt

这时我们就从tree对象找到了本次提交的文件快照信息。

3. 分支管理

新建一个分支

$ git branch test_branch

#logs文件夹发生了变化
logs:.
│  HEAD
│
└─refs
    └─heads
            master
            test_branch #新增文件

#查看test_branch
0000000000000000000000000000000000000000 8f15afaae122a0eeaabc04fb1dc3ab36e3ecbb90 username <xxx@qq.com> 1606828668 +0800    branch: Created from master
#记录了本次的新建分支操作和master分支的最后一次提交的commit对象

#同样的,refs文件夹的heads也新增了test_branch文件
8f15afaae122a0eeaabc04fb1dc3ab36e3ecbb90
#内容为最新一次提交的commit对象

执行分支切换

$ git checkout test_branch

#根目录下的HEAD文件
ref: refs/heads/test_branch
#指向了我们的当前分支

修改分支上的文件进行提交

#logs下的test_branch
8f15afaae122a0eeaabc04fb1dc3ab36e3ecbb90 67e35930423b61cbb09d90dd47e0e16bf0abbdb3 username <xxx@qq.com> 1606829497 +0800    commit: 分支提交

然后切换为主分支进行合并

$ git merge test_branch
Updating 8f15afa..67e3593
Fast-forward
 hello.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

#logs下的master
8f15afaae122a0eeaabc04fb1dc3ab36e3ecbb90 67e35930423b61cbb09d90dd47e0e16bf0abbdb3 username <xxx@qq.com> 1606829717 +0800    merge test_branch: Fast-forward

#refs下的master
67e35930423b61cbb09d90dd47e0e16bf0abbdb3
#内容也更新为了test_branch分支上的最后一次提交信息

4. 小结

经过以上操作我们不难发现,git其实是通过commit -> tree -> blob这样的三类对象组成的关系链来储存每一次的提交信息的,而commit对象与commit对象也构成了一条关系链,这样就把无数个提交信息联系了起来,而HEAD用来指示当前的工作分支,并在refs文件夹中找到这个分支的指向的最后一个commit对象。

我们平时的提交变更、切换分支等操作其实也就是改变了这其中的指向而已。

顺带一提这种哈希链的储存方式也可以很好的进行防篡改,如果你改了其中一个文件,那么文件的哈希值变了,tree的哈希值也需要变,跟着commit的哈希值也需要变,你必须改掉整个仓库的关系链。

GIT常用操作指令

1. 初始化仓库

#初始化代码仓库
$ git init

#从远程仓库拉取代码仓库,默认拉去master分支
$ git clone [url]

#从远程仓库拉取指定分支
$ git clone [url] -b [branch]

2. 工作区操作

#查看git当前文件状态,包含工作区和暂存区
$ git status

#还原指定变更文件
$ git restore [file1] [file2] ...

#还原工作区所有变更文件
$ git restore .

2. 暂存区操作

#添加指定更改文件到暂存区
$ git add [file1] [file2] ...

#添加所有更改文件到暂存区
$ git add .

#撤销暂存区指定文件(HEAD不区分大小写)
$ git restore --staged [file1] [file2] ...

#撤销暂存区所有文件
$ git restore --staged .

3. 仓库区操作

#提交指定暂存区文件到仓库区
$ git commit [file1] [file2] ... -m [message]

#提交所有暂存区文件到仓库区
$ git commit -m [message]

#查看最近提交信息,可得到commit哈希值
$ git log

#回退一个版本,提交的文件会回到暂存区
$ git reset --soft HEAD~1

#回滚到指定的commit版本
$ git reset --hard [commit_id]

#用新的commit回滚到指定的commit版本,此次回滚和之前的commit都会保留
$ git revert [commit_id]

4. 分支操作

#列出所有本地分支,-r列出所有远程分支,-a列出本地和远程所有分支
$ git branch

#新建一个分支
$ git branch [branch]

#切换到指定分支
$ git checkout [branch]

#从指定分支新建一个分支,并切换到该分支
$ git checkout -b [branch] [orgin_branch]

#合并指定分支到当前分支
$ git merge [branch]

5. 标签操作

#列出所有tag
$ git tag

#新建一个tag在当前commit
$ git tag [tag]

#查看tag信息
$ git show [tag]

#推送指定tag到远程
$ git push origin [tag]

#删除本地tag
$ git tag -d [tag]

#删除远程tag
$ git push origin :refs/tags/[tagName]

6. 远程仓库操作

#显示所有远程仓库
$ git remote -v

#拉取远程仓库指定分支,与本地分支进行合并
$ git pull orgin [branch]

#上传指定本地分支到远程仓库
$ git push orgin [branch]

总结

在这个IT行业快速的时代,大部分的需求都是有现成的工具和解决方案可以参考和使用,但是大部分人往往只是局限在于用上,并不会想着去了解实现原理,更不会有想法去优化它,这其实是一个很危险的信号,会使用这些工具是一方面,掌握这些工具的实现思想才是最重要的一点。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • Git是一个快速,可扩展的分布式版本控制系统。从根本上来说,Git是一个内容寻址(content-addressa...
    kawa007阅读 955评论 0 0
  • 1 前言 Git使用比较灵活,达到相同结果有多种方式。 靠记忆不同场景下的命令组合,会停留在“知其然,不知其所以然...
    此间有道阅读 472评论 0 0
  • 前言 从工作开始就一直使用git命令, clone,checkout, branch等,但是一直不知道为什么提交代...
    violet_syls阅读 369评论 0 0
  • 朋友整理的,放这里偶尔过来看看 一、基本介绍 首先,Git作为版本控制系统,他的原理与SVN为首的集中式版本控制系...
    allenzhan阅读 976评论 0 3
  • 黑色的海岛上悬着一轮又大又圆的明月,毫不嫌弃地把温柔的月色照在这寸草不生的小岛上。一个少年白衣白发,悠闲自如地倚坐...
    小水Vivian阅读 3,093评论 1 5