阅读 161

她来了,她来了,她带着「Git 原理」走来了

一如既往的开场白

酱酱酱酱!Hello 大家好 我是你们的梨花酱 好久不见 我带着干货又回来了!因为 Git 是我们常用的代码协作工具 正好近期学习了 Git 原理 特来和大家分享~ 下面多图预警 ❗️❗️❗️

前言

首先 我们熟悉一下 git 的核心概念

  • 拥有 Working Directory、Staging Area 和 Local Repository 三个分区
  • Local Repository 以 object 形式存储数据,object 分为三种类型
    • commit
    • tree
    • blob
  • commit 可视化以后是一个有向无环图(DAG-Directed Acyclic Graph),每个箭头都往前指

1 初始化

以上命令分为三步:

    1. 创建一个新的文件夹 git-demo
    1. 进入 git-demo 目录下
    1. 初始化项目,以进行版本管理

git init 命令的作用就是在当前目录下创建一个 .git 子文件夹,用来保存版本信息

2 查看 .git

温馨提示:先执行 brew install tree 下载 tree command 方便查看文件夹的树形结构

如上图所示,.git 目录下还有一些子目录,下面解释几个重点目录的含义👇

  • hooks---钩子,执行各种操作的时候会先执行这个钩子,可以做事先的校验或事后的处理,比如提交前检查提交信息等。这个目录下可以存放 git 脚本,一般用于规范 git 操作。
  • info---存放配置,比如 .gitignore
  • objects---数据存储,存放所有 git objects。其包含三种对象类型:blob、tree、commit
  • refs---存放远端以及各个分支的信息、版本信息、本地所有分支的 HEAD 指针

3 数据存储 --- objects 目录

3.1 创建并提交一个文件

创建并提交一个文件到本地仓库

3.2 查看 objects 目录

如上图红框所示,此目录下增加了三个子目录,分别是9b、e6 和 fc,每个子目录下都存放着一个名字巨长(38位字母)的文件

git 以键值对存储内容,key 值使用 SHA1 算法生成40位哈希值,前两位作为目录名,后38位作为该目录下的文件名,value 以二进制形式存储在该文件里

3.3 查看 object 类型

通过观察,我们发现创建并提交一个文件到本地仓库,会在 objects 目录下新生成三个 object,分别是 commit、blob 和 tree 类型

git cat-file [-t], -t 可以查看 object 的类型

3.4 查看 object 内容

  • 9b1445 是 commit 类型的 object,一般存储着 tree、parent、author 和 committer等信息(注意,因为是第一个提交,所以没有 parent 信息
  • e69de2 是 blob 类型的 object,一般存储着文件的内容。(注意,因为 hello.txt 中没有写入内容,所以此处没有打印为空
  • fcb545 是 tree 类型的 object,一般存储着 blob object 的权限、类型、SHA1哈希值以及文件名信息

git cat-file [-p], -p 可以查看 object 的内容

3.5 可视化 object 关系

为了方便表述,以下的 commit、tree、blob 分别指 commit object、tree object、blob object

上图表示之前一系列操作后得到的可视图,由此可见 commit 存储着 tree,tree 存储着 blob。那么,一个 commit 能存储几个 tree 呢?tree 可以存储 tree 吗?别急,我们下面揭晓

  • 一个 commit 存储一个 tree ,parent 存储上一次提交的 commit ( 图中用向前指的 parent 箭头表示
  • tree 可以存储 N 个 blob, M 个 tree ( N >= 1,M >= 0

Q:为什么需要 tree,commit 不能直接存储 blob 信息吗?

A:因为需要利用 tree 来存储 blob 的权限和命名等。在文件重命名的场景中,tree 的作用就体现出来了。假如没有 tree ,将文件名保存在 blob 中,那么 git 只能复制原始内容加上新命名形成一个新的 blob。假如有 tree,这个时候就只需要更新 tree 中对应的文件名,原来的 blob 就得以复用,节约了空间。

review:git 是以 object (对象)的形式存储数据的,位于 .git 目录下的 objects 子目录中


接下来我们对比一下 git 常用命令之间的区别

推荐:👉 Learn Git Branching 👈 这个网站可以可视化练习 git 的使用(是那种有趣的游戏闯关形式哦

分支合并 ---- git merge 和 git rebase

注:以下 gif 动图中,分为 topic 分支和 master 分支,在 topic 分支上执行 git merge/rebase master

git merge:优势是会保留详细的合并信息,但是会产生一个新的 merge commit 节点,

  • fast-forward: 发生在 master 分支的状态没有被更改过

  • non fast-forward:发生在 master 分支的历史记录自 topic 分支分叉出去后有新的更新

git rebase:优势是可以创造更线性的提交历史,不过 commit 的提交顺序会被打乱

撤销更改 --- git revert 和 git reset

  • git revert 用于“反做”某一个 commit,相当于撤销其操作。用场景是想撤销之前的某一个 commit ,但是又想保留该 commit 之后的所有 commit 。

举个🌰,在 pushed 分支上,我们有 c2、c6、c7三个提交,现在,我们想撤销 c2,但是又想保留 c6 和 c7。这个时候就可以使用 git revert c2,生成新的 commit c2' ,c2' 中保留了 c6 和 c7 的代码提交但是撤销了 c2 中的代码修改。这种形式的撤销支持将更改推送到远程仓库与别人分享。

  • git reset 改写历史,向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

举个🌰,在 local 分支上,我们有 c3、c4、c5 三个提交,现在,我们想重置提交到 c3,c4 和 c5 都不要了。这个时候就可以使用 git reset c3 ,c3 之后的提交都不见了!(注意,reset 默认使用 mixed 模式 ,即 c4 和 c5 所做的变更还在,只不过保存在 working directory ,还未加入 stage area。 详情见 git reset 的三种模式

拉取代码 --- git fetch 和 git pull

从阮大神博客扒来的一张图,大神已经画的很详细了,我就不再画蛇添足了

下图出处

  • git fecth 相当于将远端代码拉取到本地仓库,更新本地仓库的代码
  • git pull 相当于直接将远端代码拉取到工作目录,默认等价于执行了 fetch + merge

git best practice :和远端仓储同步时,尽量使用 git pull --rebase 而不是 git pull 。或者使用 git fetch,再根据个人需要,使用 rebase 或 merge 同步修改

结束语

看资料 + 编辑 + 画图,这篇文章差不多花了两天写完,还是收货满满~ 毕竟是第一次学习 git 原理,文中有些表述可能有误,欢迎大家积极指正。最后,希望看完这篇文章的你能有所收获哦!

参考资料