通俗易懂的可视化 Git 分支管理教程——Learn Git Branching.
特点:将知识点设计成关卡,每一步操作都有可视化效果展示,附带一个完全自由的沙盒调试玩耍。
具体操作就不谈了,非常容易上手,下文仅给自己做些笔记。
![](https://s3.ax1x.com/2020/12/10/rFYu01.jpg)
主要
1.1 Git Commit
Git 仓库中的提交记录保存的是你的目录下所有文件的快照。Git 希望提交记录尽可能地轻量,因此在你每次进行提交时,它并不会盲目地复制整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。
Git 还保存了提交的历史记录,父节点是当前提交中变更的基础。对于项目组的成员来说,维护提交历史对大家都有好处。
git commit
创建一个新的提交记录。
1.2 Git Branch
Git 的分支也非常轻量。它们只是简单地指向某个提交纪录 —— 仅此而已。所以许多 Git 爱好者传颂:早建分支!多用分支!这是因为即使创建再多的分支也不会造成储存或内存上的开销,并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。
在将分支和提交记录结合起来后,我们会看到两者如何协作。现在只要记住使用分支其实就相当于在说:“我想基于这个提交以及它所有的父提交进行新的工作。”
git branch newImage
创建一个名为 newImage 的分支。
分支名后带一个*
表示当前分支,commit 的时候会基于当前分支提交。
git checkout <name>
切换到新的分支上。
git checkout -b <name>
创建一个新的分支同时切换到新创建的分支。
1.3 Git Merge
接下来咱们看看如何将两个分支合并到一起,就是说我们新建一个分支,在其上开发某个新功能,开发完成后再合并回主线。
第一种方法 —— git merge
。在 Git 中合并两个分支时会产生一个特殊的提交记录,它有两个父节点。翻译成自然语言相当于:“我要把这两个父节点本身及它们所有的祖先都包含进来。”
假设当前分支为master(C3)
,使用git merge bugFix
将bugFix
合并到master
里。
![](https://s3.ax1x.com/2020/12/10/rFdQzT.png)
为了帮助学习理解,我引入了颜色搭配。每个分支都有不同的颜色,而每个节点(提交记录)的颜色是所有包含该节点的分支颜色的混合。
所以,C3、C4 是master
分支的颜色,其他的节点则是master
和bugFix
分支颜色的混合。
git checkout bugFix; git merge master
,再把master
分支合并到bugFix
。
因为master
继承自bugFix
,Git 什么都不用做,只是简单地把bugFix
移动到master
所指向的那个提交记录。
现在所有提交记录的颜色都一样了,这表明每一个分支都包含了代码库的所有修改!
1.4 Git Rebase
第二种合并分支的方法是git rebase
。Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个地放下去。
Rebase
的优势就是可以创造更线性的提交历史。
假设当前所在的分支是bugFix(C3)
,git rebase master
把bugFix
分支里的工作直接移到master
分支上。移动以后会使得两个分支的功能看起来像是按顺序开发,但实际上它们是并行开发的。
![](https://s3.ax1x.com/2020/12/10/rFs2kQ.png)
注意,提交记录C3
依然存在(树上那个半透明的节点),而 C3’是我们Rebase
到master
分支上的C3
的副本。
现在唯一的问题就是master
还没有更新。切换到 master
上,git rebase bugFix
把它 rebase 到 bugFix 分支上。由于 bugFix
继承自 master
,所以 Git 只是简单的把 master
分支的引用向前移动了一下而已。
2.1 分离 HEAD
HEAD 是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。
HEAD 通常情况下是指向分支名的(如bugFix
),提交时 HEAD 会跟着变化。
分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名,一般用哈希值来指定提交记录。
命令前:HEAD -> master -> C1
命令:git checkout C1
命令后:HEAD -> C1
2.2 & 2.3 相对引用
通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时,并没有像本程序中这么漂亮的可视化提交树供你参考,所以你就不得不用 git log
来查查看提交记录的哈希值。并且哈希值在真实的 Git 世界中也会更长(译者注:基于 SHA-1,共 40 位)。例如前一关的介绍中的提交记录的哈希值可能是 fed2da64c0efc5293610bdd892f82a58e8cbc5d8
,舌头都快打结了吧。比较令人欣慰的是,Git 对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可。因此我可以仅输入fed2
而不是上面的一长串字符。
Git 引入了相对引用:
- 使用
^
向上移动 1 个提交记录 - 使用
~<num>
向上移动多个提交记录,如~3
首先看看操作符^
。 master^
相当于master
的父节点,master^^
是 master
的第二个父节点。也可以将 HEAD 作为相对引用的参照:git checkout HEAD^
,使用 HEAD^ 向上移动。
~
操作符后面可以跟一个数字(可选,不跟数字时与 ^
相同,向上移动一次),指定向上移动多少次,例如git checkout HEAD~4
一次后退四步。
使用相对引用最多的就是移动分支。可以直接使用 -f
选项让分支指向另一个提交。例如git branch -f master HEAD~3
将master
分支强制指向 HEAD 的第 3 级父提交。
2.4 撤销变更
git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。
![](https://s3.ax1x.com/2020/12/10/rFIe0A.png)
git reset HEAD~1
把 master 分支移回到 C1;现在我们的本地代码库根本就不知道有 C2 这个提交了。
(译者注:在 reset 后, C2 所做的变更还在,但是处于未加入暂存区状态。)
虽然在你的本地分支中使用 git reset 很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦!为了撤销更改并分享给别人,我们需要使用 git revert。
![](https://s3.ax1x.com/2020/12/10/rF4jat.png)
git revert HEAD
新提交记录 C2’ 引入了更改 —— 这些更改刚好是用来撤销 C2 这个提交的。也就是说 C2’ 的状态与 C1 是相同的。revert
之后就可以把你的更改推送到远程仓库与别人分享啦。
远程
1.1 Git Clone
远程仓库特点——备份、社交化。
直到现在,教程都聚焦于本地仓库的操作(branch、merge、rebase 等等)。但我们现在需要学习远程仓库的操作 —— 我们需要一个配置这种环境的命令,它就是 git clone
。
![](https://s3.ax1x.com/2020/12/10/rFo14x.png)
1.2 远程分支
git clone
之后,本地仓库多了一个名为 o/master
的分支,这种类型的分支就叫远程分支,其拥有一些特殊属性。
远程分支有一个命名规范 —— 它们的格式是:<remote name>/<branch name>
。因此,如果你看到一个名为 o/master
的分支,那么这个分支就叫 master
,远程仓库的名称就是 o
。大多数的开发人员会将它们主要的远程仓库命名为 origin
,为了简化 UI,这里我们用简写 o
。
远程分支反映了远程仓库“在你上次和它通信时”的状态。
远程分支有一个特别的属性,在你检出checkout
时自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因,你必须在别的地方完成你的工作,(更新了远程分支之后)再用远程分享你的工作成果。
![](https://s3.ax1x.com/2020/12/10/rF7Jne.png)
1.3 Git Fetch
从远程仓库获取数据 —— git fetch
。
![](https://s3.ax1x.com/2020/12/10/rF7j9x.png)
git fetch
实际上将本地仓库中的远程分支指针o/master
更新成了远程仓库相应分支最新的状态。
git fetch
通常通过互联网(使用 http://
或 git://
协议) 与远程仓库通信。
git fetch 并不会改变你本地仓库的状态。它不会更新你的 master 分支,也不会修改你磁盘上的文件。可以理解成git fetch
把远程仓库下载到了本地副本,但还没有合并。
![](https://pic3.zhimg.com/v2-af3bf6fee935820d481853e452ed2d55_r.jpg?source=1940ef5c)
可以配合知乎这篇回答里的图片理解。
git pull 和 git fetch 的区别? - 波罗学的回答 - 知乎 https://www.zhihu.com/question/38305012/answer/625881308
1.4 Git Pull
如何进行合并?可以有以下方式:
- git cherry-pick o/master
- git rebase o/master
- git merge o/master
由于先抓取更新、再合并到本地分支这个流程很常用,因此 Git 提供了一个专门的命令来完成这两个操作——git pull
。它相当于git fetch
+git merge
。
![](https://s3.ax1x.com/2020/12/10/rFbuz6.png)
1.5 模拟团队合作
引入一个自造命令 git fakeTeamwork
——“假装”你的同事、朋友、合作伙伴更新了远程仓库。
例如git fakeTeamwork foo 3
指定 3 个提交记录到远程仓库的 foo 分支。
1.6 Git Push
git push
负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。
![](https://s3.ax1x.com/2020/12/10/rFbHYR.png)
1.7 偏离的提交历史
假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 —— 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。
这种情况(历史偏离)有许多的不确定性,Git 会强制你先合并远程最新的代码,然后才能分享你的工作。
这里用 rebase 方法或者 merge 方法都行,特别的,git pull
默认是fetch
+merge
,而git pull --rebase
可以理解成fetch
+rebase
。
![](https://s3.ax1x.com/2020/12/10/rFqaN9.png)
![](https://s3.ax1x.com/2020/12/10/rFqB1x.png)
1.8 锁定的 Master(Locked Master)
如果你是在一个大的合作团队中工作,很可能是 master 被锁定了,需要一些 Pull Request 流程来合并修改。如果你直接提交 (commit) 到本地 master, 然后试图推送 (push) 修改,你将会收到这样类似的信息:
! [远程服务器拒绝] master -> master (TF402455: 不允许推送 (push) 这个分支;你必须使用 pull request 来更新这个分支。)
解决方案:新建一个分支 feature, 推送到远程服务器。然后 reset 你的 master 分支和远程服务器保持一致,否则下次你 pull 并且他人的提交和你冲突的时候就会有问题。
2.1 推送主分支
在大型项目中开发人员通常会在(从 master 上分出来的)特性分支上工作,工作完成后只做一次集成。这跟前面课程的描述很相像(把 side 分支推送到远程仓库),不过本节我们会深入一些。 但是有些开发人员只在 master 上做 push、pull —— 这样的话 master 总是最新的,始终与远程分支 (o/master) 保持一致。
2.2 合并远程仓库
在开发社区里,有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点: 优点:Rebase 使你的提交树变得很干净,所有的提交都在一条线上 缺点:Rebase 修改了提交树的历史 比如,提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的,但实际上是在 C3 之前。 一些开发人员喜欢保留提交历史,因此更偏爱 merge。而其他人(比如我自己)可能更喜欢干净的提交树,于是偏爱 rebase。仁者见仁,智者见智。:D