前面几天介绍的Git操作,接下来的几天将会分享一些Git中分支的用法。很多版本控制系统中都有分支的概念,创建分支意味着在偏离主分支并在特定的分支上完成自己的开发,从而不影响项目的主分支的开发。
Git中分支与其他版本控制系统的分支不同,Git的分支功能轻量化打了极致,分支的创建、删除、切换等操作几乎能够瞬时完成。在介绍Git分支之前有必要先来深入剖析 `Git是如何存储数据的。
1 Git对象
在《Git入门》这篇文章中,我们了解到了Git中的三个区:工作区、暂存区和版本库。当发起提交的时候,Git会将暂存区的内容提交到版本库。
当使用命令git log查看提交历史时,其输出内容中,在commit后总会有SHA-1校验和。那么,Git中由一串40位十六进制字符串组成的校验和,为什么能够表示很多类型的内容,比如:提交对象、文件内容等。
这次还是以vue3源代码仓库为例,来探索Git中的对象。
$ git log -1 --pretty=rawcommit 310cb8cfab7596edd7185f0957e43dd0e797cee5tree 4658cf65b0c6aa52a5497d137f39b7c7232d5044parent 4fecb27f8696fdb8f681948543ea81ea62fe43bfauthor Evan You <yyx990803@gmail.com> 1610999501 -0500committer Evan You <yyx990803@gmail.com> 1611950632 -0500 chore: fix useSSRContext casing in warning message
以上的输出内容就包含了3个SHA-1校验和,从上往下依次是:
-
本次提交的唯一表示:
310cb8cfab7596edd7185f0957e43dd0e797cee5 -
本次提交对应的树对象:
4658cf65b0c6aa52a5497d137f39b7c7232d5044 -
本次提交的父提交:
4fecb27f8696fdb8f681948543ea81ea62fe43bf
1.1 查看对象类型
使用命令git cat-file -t <id>查看对象ID所对应的类型,例如:
$ git cat-file -t 310cbcommit$ git cat-file -t 4658ctree$ git cat-file -t 4fecb27commit
在Git命令中引用对象ID时,没有必要输出全部的40位校验和,只需要提供不重复的前几位字符就行,这里的不重复对应的范围是当前工作空间。
1.2 查看对象内容
使用命令git cat-file -p <id>查看对象ID所对应的内容,例如:
$ git cat-file -p 310cbtree 4658cf65b0c6aa52a5497d137f39b7c7232d5044parent 4fecb27f8696fdb8f681948543ea81ea62fe43bfauthor Evan You <yyx990803@gmail.com> 1610999501 -0500committer Evan You <yyx990803@gmail.com> 1611950632 -0500chore: fix useSSRContext casing in warning message$ git cat-file -p 4658c040000 tree f46e8212ca6adb9797842290a7409e5991337ebb .circleci100644 blob 98f42a74b9a168d05d4ec44445fd9d05a19534e5 .eslintrc.js040000 tree 0e2c32c16217f7bd11b0b39559b373967e011571 .github100644 blob 0a663edeb54caf4c87bce21c0ed3840b92c7c48b .gitignore100644 blob f5a1bdcdd2daf271a87314a475c36ca723c0b499 .prettierrc040000 tree 10b55d6ccfbb4907dbb7b7c0eb599b0b2e138be9 .vscode100644 blob bad153fd69eaacf705eab6c03bde75a2a7f11ac6 CHANGELOG.md100644 blob 15f1f7e7a490fec6e4a4016624325debfd9458a9 LICENSE100644 blob fa14c5dd8b2a4690d65c92c3018e4edcfd3056e0 README.md100644 blob 883bf47a3ae59e23db0a1d2673653159e79a3837 api-extractor.json100644 blob e6f315c6de12f45aeeacec8585c764f1e5598d37 jest.config.js100644 blob 8b766cffccbee115aa407079b8a70427b42b97b4 package.json040000 tree c15f34a475133115b26d719f3c9e63378c95ae69 packages100644 blob 7c5cc05c55ab2356915e5dc44eb6476707aed050 rollup.config.js040000 tree 26e371f4e3536b1891324b49bda9d265b19d191c scripts040000 tree 4e7603c03699913ccb89db1f35462a326d978049 test-dts100644 blob 117bce3edf97e712f62f084e769de5a8ce44e7c4 tsconfig.json100644 blob fc4dbbca00ad055f4df1bdc9b54e167f02f65822 yarn.lock
上述的输出结果表明:
-
commit对象内容中包括了此次提交的ID、父提交的ID、创建者名称和邮箱、提交者名称和邮箱以及对应的提交信息; -
tree对象内容中包括了此次提交了哪些文件,如果继续使用命令git cat-file查看blob对象内容,输出的该文件的内容,例如,查看tsconfig.json文件对应的对象ID:$ git cat-file -p 117bce3edf97e712f62f084e769de5a8ce44e7c4{ "compilerOptions": { "baseUrl": ".", "outDir": "dist", "sourceMap": false, "target": "esnext", "module": "esnext", "moduleResolution": "node", "allowJs": false, "strict": true, "noUnusedLocals": true, "experimentalDecorators": true, "resolveJsonModule": true, "esModuleInterop": true, "removeComments": false, "jsx": "preserve", "lib": ["esnext", "dom"], "types": ["jest", "puppeteer", "node"], "rootDir": ".", "paths": { "@vue/": ["packages//src"], "vue": ["packages/vue/src"] } }, "include": [ "packages/global.d.ts", "packages//src", "packages/runtime-dom/types/jsx.d.ts", "packages//tests", "test-dts" ]}
1.3 对象存储
在Git中,上述对象存储在.git目录中的objects子目录中,其中,ID的前2位作为目录名称,后面的38位作为文件名。
下图清楚地显示了命令git log -1 --pretty=raw命令输出结果中各个对象的关系。
还记得之前提到的git log --graph命令吗?它以简单图表的方式显示Git提交的历史,其实,它的原理就是追踪提交对象之间的关系链,这条关系链的起点,就是当前仓库的第一次提交。
1.4 master与HEAD
前几篇内容中多次提到了master这个名词,而且,也描述了这个词的含义:通常情况下表示默认分支的名称。HEAD这个名词之前的内容中基本没有出现,但是在接下来的内容中会经常使用到。这里从Git对象的角度对master和HEAD进行一个阐述。
首先,使用git log命令查看master和HEAD对应的什么内容。
$ git log -1 HEADcommit 310cb8cfab7596edd7185f0957e43dd0e797cee5 (HEAD -> master, origin/master, origin/HEAD)Author: Evan You <yyx990803@gmail.com>Date: Mon Jan 18 14:51:41 2021 -0500 chore: fix useSSRContext casing in warning message$ git log -1 mastercommit 310cb8cfab7596edd7185f0957e43dd0e797cee5 (HEAD -> master, origin/master, origin/HEAD)Author: Evan You <yyx990803@gmail.com>Date: Mon Jan 18 14:51:41 2021 -0500 chore: fix useSSRContext casing in warning message
上述的输出内容表明了,在当前版本库中,HEAD和master都指向同一个对象,接下来,来看一下在.git目录中它们有是什么情况呢?
在根目录中,执行命令find .git -name HEAD -o -name master:
$ find .git -name HEAD -o -name master.git/refs/heads/master.git/refs/remotes/origin/HEAD.git/HEAD.git/logs/refs/heads/master.git/logs/refs/remotes/origin/HEAD.git/logs/HEAD
抛开logs目录下的内容,浏览以下HEAD中内容。
$ cat .git/HEADref: refs/heads/master
这句话怎么理解呢?它是表示:HEAD已用了refs/heads/master中的内容。
$ cat .git/refs/heads/master310cb8cfab7596edd7185f0957e43dd0e797cee5
上面的输出表示的是什么内容呢?
$ git cat-file -t 310cbcommit$ git cat-file -p 310cbtree 4658cf65b0c6aa52a5497d137f39b7c7232d5044parent 4fecb27f8696fdb8f681948543ea81ea62fe43bfauthor Evan You <yyx990803@gmail.com> 1610999501 -0500committer Evan You <yyx990803@gmail.com> 1611950632 -0500chore: fix useSSRContext casing in warning message
哦,明白了!!!refs/heads/master指向的是一个提交对象并且是最新的提交对象,然后HEAD又引用了master对象。想在呢,在脑海中在浮现git log --graph输出结果的内容,最上面内容是最新的提交ID,也就是HEAD和master引用的提交,然后最下面的是第一次的提交对象,中间的内容,是基于最新的提交追踪整个提交历史。
* 60f3f68adb617ce45de5225ce93b23615913bffb (HEAD -> master, tag: v1.1-alpha) version* 04ca43e116cd379eb12b2ce7929ea4b9fca50cba (tag: v1.0-alpha, tag: v1.0) 20210203<E6><8F><90><E4><BA><A4>* 0fa7c98f5ba52258d86cae093a83166299ef1dd8 <E6><B5><8B><E8><AF><95>1* 038b977a14b729270c9b49618128d77b8332e084 <C3><AF>* 58bb3ef7cfe10951929f829b3d56a3f4cfbadcd9 <E6><B5><8B><E8><AF><95>
2 分支机制简述
通过第1章中了解了Git对象的基础内容。当发起提交时,Git存储的对应的提交对象,这个提交对象中包含了指向暂存区快照的指针、提交者的名称和邮箱、提交的相关信息以及指向父提交的指针;如果是仓库的第一次提交,其父提交的指针为空;而对于上一篇文章提到的合并后的提交,它的父提交的指针会有多个。
Git的分支其实就是一个指向某次提交对象的轻量级的指针。当在一个分支内提交时,指向该分支的指针就会往前移动。Git分支本质上就是一个包含了该分支指向的最新提交对象ID(40个字符的校验和)的文件,因此Git创建和删除分支的成本很低,这也成为了Git对于其他版本控制系统的“杀手锏”特性
在多分支的情况下,Git如何知道当前实在哪个分支呢?这时候HEAD指针就起到了作用,HEAD指针一致指向当前所在本地分支的指针。
2.1 创建分支
使用命令git branch <branch_name>可以创建一个新的分支,创建的新分支的指针指向最近一次的提交对象。
2.2 切换分支
使用命令git checkout <branch_name可以在不同分支间切换,同时也切换了HEAD指针的引用,保证它能引用到本地所在分支的最新提交。