详解git中的HEAD、master 与 branch

本文主要是几个概念的解释:HEAD、master 以及 Git 中非常重要的一个概念: branch。

引用:commit 的快捷方式

首先,再看一次log:

bash 复制代码
git log

第一行的commit后面括号里的HEAD -> master, origin/master, origin/HEAD,是几个指向这个commit的引用。在 Git 的使用中,经常会需要对指定的commit进行操作。每一个commit都有一个它唯一的指定方式------它的 SHA-1 校验和,也就是上图中每个黄色的commit右边的那一长串字符。两个 SHA-1 值的重复概率极低,所以你可以使用这个 SHA-1 值来指代commit,也可以只使用它的前几位来指代它(例如第一个78bb0ab7d541...16b77,你使用78bb0ab甚至78bb来指代它通常也可以),但毕竟这种没有任何含义的字符串是很难记忆的,所以 Git 提供了「引用」的机制:使用固定的字符串作为引用,指向某个commit,作为操作commit时的快捷方式。

HEAD:当前 commit 的引用

上一段里说到,图中括号里是指向这个commit的引用。其中这个括号里的HEAD是引用中最特殊的一个:它是指向当前 commit 的引用 。所谓当前 commit这个概念很简单,它指的就是当前工作目录所对应的commit。

例如上图中的当前commit就是第一行中的那个最新的commit。每次当有新的commit的时候,工作目录自动与最新的commit对应;而与此同时,HEAD也会转而指向最新的commit。事实上,当使用checkout、reset等指令手动指定改变当前commit的时候,HEAD也会一起跟过去。

总之,当前commit在哪里,HEAD就在哪里,这是一个永远自动指向当前commit的引用,所以你永远可以用HEAD来操作当前commit。

branch

HEAD是 Git 中一个独特的引用,它是唯一的。而除了HEAD之外,Git 还有一种引用,叫做branch(分支)。HEAD除了可以指向commit,还可以指向一个branch,当它指向某个branch的时候,会通过这个branch来间接地指向某个commit;另外,当HEAD在提交时自动向前移动的时候,它会像一个拖钩一样带着它所指向的branch一起移动。

例如上面的那张图里,HEAD -> master中的master就是一个branch的名字,而它左边的箭头->表示HEAD正指向它(当然,也会间接地指向它所指向的commit)。

如果我在这时创建一个commit,那么HEAD会带着master一起移动到最新的commit:

sql 复制代码
git commit

通过查看log,可以对这个逻辑进行验证:

bash 复制代码
git log

从图中可以看出,最新的commit(提交信息:"Add feature1")被创建后,HEAD和master这两个引用都指向了它,而在上面第一张图中的后两个引用origin/master和origin/HEAD则依然停留在原先的位置。

master: 默认 branch

上面的这个master,其实是一个特殊的branch:它是 Git 的默认branch(俗称主branch/ 主分支)。

所谓的「默认 branch」,主要有两个特点:

  1. 新创建的 repository(仓库)是没有任何commit的。但在它创建第一个commit时,会把master指向它,并把HEAD指向master。
  1. 当有人使用git clone时,除了从远程仓库把.git这个仓库目录下载到工作目录中,还会checkout(签出)master(checkout的意思就是把某个commit作为当前commit,把HEAD移动过去,并把工作目录的文件内容替换成这个commit所对应的内容)。

另外,需要说一下的是,大多数的开发团队会规定开发以master为核心,所有的分支都在一定程度上围绕着master来开发。这个在事实上构成了master和其它分支在地位上的一个额外的区别。

branch 的通俗化理解

尽管在 Git 中,branch只是一个指向commit的引用,但它有一个更通俗的理解:你还可以把一个branch理解为从初始commit到branch所指向的commit之间的所有commits 的一个「串」。例如下面这张图:

master的本质是一个指向3的引用,但你也可以把master理解为是123三个commit的「串」,它的起点是1,终点是3。

这种理解方式比较符合branch这个名字的本意(branch 的本意是树枝,可以延伸为事物的分支),也是大多数人对branch的理解。不过如果你选择这样理解branch,需要注意下面两点:

  1. 所有的branch之间都是平等的。
go 复制代码
例如上面这张图,`branch1` 是 `1` `2` `5` `6` 的串,而不要理解为 `2` `5` `6` 或者 `5` `6` 。其实,起点在哪里并不是最重要的,重要的是你要知道,所有 `branch` 之间是平等的,`master` 除了上面我说的那几点之外,并不比其他 `branch` 高级。这个认知的理解对于 `branch` 的正确使用非常重要。

换个角度来说,上面这张图我可以用别的画法来表达,它们的意思是一样的:

通过这张动图应该能够对「平等」这个概念更好地理解了吧?

  1. branch包含了从初始commit到它的所有路径,而不是一条路径。并且,这些路径之间也是彼此平等的。
go 复制代码
像上图这样,`master` 在合并了 `branch1` 之后,从初始 `commit` 到 `master` 有了两条路径。这时,`master` 的串就包含了 `1` `2` `3` `4` `7` 和 `1` `2` `5` `6` `7` 这两条路径。而且,这两条路径是平等的,`1` `2` `3` `4` `7` 这条路径并不会因为它是「原生路径」而拥有任何的特别之处。

如果你喜欢用「树枝」的概念来理解 Git 的branch,一定要注意上面说的这两点,否则在今后使用branch的时候就可能与出现理解偏差或者使用方式不当的问题。事实上我本人并不喜欢用这种方式来理解branch,因为觉得它有点舍近求远的味道:我为了「直观」地思考,给它了一个形象的比喻,但由于它的本质含义其实更加简单,导致我的这种比喻反而增加了思考它时的复杂度,未免有点画蛇添足。不过这是我自己的感受,怎么理解branch是个个人偏好的问题,这两种理解方式你选一个喜欢的就好。

branch 的创建、切换和删除

创建 branch

如果你想在某处创建branch,只需要输入一行git branch 名称。例如你现在在master上:

你想在这个commit处创建一个叫做 "feature1" 的branch,只要输入:

复制代码
git branch feature1

你的branch就创建好了:

切换 branch

不过新建的branch并不会自动切换,你的HEAD在这时依然是指向master的。你需要用checkout来主动切换到你的新branch去:

复制代码
git checkout feature1

然后HEAD就会指向新建的branch了:

除此之外,你还可以用git checkout -b 名称来把上面两步操作合并执行。这行代码可以帮你用指定的名称创建branch后,再直接切换过去。还以feature1为例的话,就是:

css 复制代码
git checkout -b feature1

在切换到新的branch后,再次commit时HEAD就会带着新的branch移动了:

sql 复制代码
git commit

而这个时候,如果你再切换到master去commit,就会真正地出现分叉了:

sql 复制代码
git checkout master
...
git commit

删除 branch

删除branch的方法非常简单:git branch -d 名称。例如要删除feature1这个 branch:

复制代码
git branch -d feature1

需要说明的有两点:

  1. HEAD指向的branch不能删除。如果要删除HEAD指向的branch,需要先用checkout把HEAD指向其他地方。
  2. 由于 Git 中的branch只是一个引用,所以删除branch的操作也只会删掉这个引用,并不会删除任何的commit。(不过如果一个commit不在任何一个branch的「路径」上,或者换句话说,如果没有任何一个branch可以回溯到这条commit(也许可以称为野生commit?),那么在一定时间后,它会被 Git 的回收机制删除掉。)
  3. 出于安全考虑,没有被合并到master过的branch在删除时会失败(因为怕你误删掉「未完成」的branch啊):
go 复制代码
这种情况如果你确认是要删除这个`branch`(例如某个未完成的功能被团队确认永久毙掉了,不再做了),可以把`-d`改成`-D`,小写换成大写,就能删除了。

「引用」的本质

所谓「引用」(reference),其实就是一个个的字符串。这个字符串可以是一个commit的 SHA-1 码(例:c08de9a4d8771144cd23986f9f76c4ed729e69b0),也可以是一个branch(例:ref: refs/heads/feature3)。

Git 中的HEAD和每一个branch以及其他的引用,都是以文本文件的形式存储在本地仓库.git目录中,而 Git 在工作的时候,就是通过这些文本文件的内容来判断这些所谓的「引用」是指向谁的。

小结

这一节介绍了 Git 中的一些「引用」:HEAD、master、branch。这里总结一下:

  1. HEAD是指向当前commit的引用,它具有唯一性,每个仓库中只有一个HEAD。在每次提交时它都会自动向前移动到最新的commit。
  2. branch是一类引用。HEAD除了直接指向commit,也可以通过指向某个branch来间接指向commit。当HEAD指向一个branch时,commit发生时,HEAD会带着它所指向的branch一起移动。
  3. master是 Git 中的默认branch,它和其它branch的区别在于:
    1. 新建的仓库中的第一个commit会被master自动指向;
    2. 在git clone时,会自动checkout出master。
  1. branch的创建、切换和删除:
    1. 创建branch的方式是git branch 名称或git checkout -b 名称(创建后自动切换);
    2. 切换的方式是git checkout 名称;
    3. 删除的方式是git branch -d 名称。**
相关推荐
灵犀学长几秒前
解锁HTML5页面生命周期API:前端开发的新视角
前端·html·html5
江号软件分享9 分钟前
轻松解决Office版本冲突问题:卸载是关键
前端
致博软件F2BPM16 分钟前
Element Plus和Ant Design Vue深度对比分析与选型指南
前端·javascript·vue.js
慧一居士1 小时前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead1 小时前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码7 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子7 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年7 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子7 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina7 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试