详解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 名称。**
相关推荐
web1350858863512 分钟前
前端node.js
前端·node.js·vim
m0_5127446413 分钟前
极客大挑战2024-web-wp(详细)
android·前端
潜意识起点37 分钟前
精通 CSS 阴影效果:从基础到高级应用
前端·css
奋斗吧程序媛41 分钟前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256563 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@3 小时前
HTML5适配手机
前端·html·html5
@解忧杂货铺5 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
F-2H7 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
gqkmiss7 小时前
Chrome 浏览器插件获取网页 iframe 中的 window 对象
前端·chrome·iframe·postmessage·chrome 插件