3. 分支管理
Git 分支和其他 VCS 相比有何优势?
在使用分支之前我们有必要先了解一下什么是分支?Git 的分支和其他版本控制系统的分支有何不同?
几乎所有的版本控制系统都以某种形式支持分支。使用分支意味着你可以把当前的工作从开发主线上分离出来,以免影响开发主线。
在其他版本控制系统中分支常常需要创建一个源代码的完整副本,这种做法对于大型项目来说是非常耗费时间的。
相对于其他版本控制系统来说,Git 的分支无疑是非常轻量的,甚至有人把 Git 的分支模型称为它的“必杀技特性”,也正是因为这一特性,才让 Git 在版本控制系统中脱颖而出,后面居上。
为何 Git 的分支模型会如此出众呢?Git 处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。与许多其它版本控制系统不同,Git 鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。理解和精通这一特性,你便会意识到 Git 是如此的强大而又独特,并且从此真正改变你的开发方式。
分支的本质是什么?
为了真正理解 Git 处理分支的方式,我们需要讲一下 Git 是如何保存数据的。 Git 保存的不是文件的变化或者差异,而是一系列不同时刻的快照。
在进行提交操作时,Git 会保存一个提交对象(commit object)。知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象,
为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和,然后会把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交。
当使用 git commit
进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后 在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息 外,还包含指向这个树对象(项目根目录)的指针。 如此一来,Git 就可以在需要的时候重现此次保存的快照。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之 后,你其实已经有一个指向最后那个提交对象的 master 分支。 master 分支会在每次提交时自动向前移动。
工作流程
基于 git 强大的分支性能,实际工作中你的工作流可能是这样的:
- 你在开发某个项目
- 有了一个新需求,你需要创建一个分支
- 在这个分支中进行开发
但正在这个时候,你突然接到通知说线上有一个很严重的问题,需要立即修复。这时你将经历下面的流程:
- 根据给你的 commitid 或分支名切换到线上版本对应的分支
- 为这个问题新建一个分支,并在新分支中修复问题
- 在测试通过后,切回线上版本对应分支,然后合并这个修复问题的分支并把改动推送到服务端。
- 基于的修复问题后的代码重新发布版本,确认无误后把改动合并到主分支,并删除问题修复分支。
- 切回你之前正在开发的需求分支,继续工作。
查看分支
在进行单分支开发模式时只有一个 master
或 main
分支,在这种情况下不需要关心分支。
但在大多数项目中都是 master 分支保持不动,在开发时每个需求是一个分支,每个 bug 也是一个分支,这就导致我们的项目中可能有成千上万个分支,这个情况下如果查看都有哪些分支呢,使用 git branch
可以查看分支
# 查看本地分支
git branch
# 查看全部分支,包含本地和远程。
git branch -a
# 查看提交情况
git show 分支名|commitid|标签名
使用 git show
命令可以查看详细的提交情况,包括对应的分支、最后的 commitid、最后提交的作者和备注等信息,可以在出现 bug 时根据线上版本的 commitid 等信息查看最后一次提交的修改内容以便定位 bug 来源。
新建和切换分支
在开始新需求或修复新 bug 前都需要基于某个分支或 commitid 新建分支,这时可以使用下面的命令达到新建分支的目的。
# 用法
git branch 分支名 # 基于当前分支新建一个分支
git branch 分支名 commitid|分支名 # 基于指定的commitid或分支名创建分支
git checkout 分支名 # 切换到新建的分支
使用 git branch 分支名
新建分支以后当前的工作区并不会自动切换到新建的分支,再使用 git checkout 分支名
可以切换到新建的分支。
也可以使用 -b
参数新建分支以后自动切换到对应的分支中去。
# 创建并自动切换到新建的分支
git checkout -b 分支名
# 基于指定的commitid或分支创建分支并自动切换过去
git checkout -b 分支名 commitid|分支名
合并分支
在一个需求验收完毕,或一个 bug 发布完毕以后,需要把需求开发和 bug 修复的分支合并到主分支中。
# 合并分支1和分支2到当前分支
git merge 分支1|commitid1 分支2|commitid2
冲突解决
如果有两个或两个以上分支修改了同一个文件,在合并分支的时候就会提交修改冲突。我们可以根据提示打开文件修改
CONFLICT (add/add): Merge conflict in 2
Auto-merging 2
Automatic merge failed; fix conflicts and then commit the result.
如图 1 中,提示在自动合并“2”这个文件时失败
<<<<<<< HEAD
222
=======
22222
>>>>>>> b2
查看文件内容我们可以看到当前分支的内容是 222,而来自 b2 分支的内容是 22222,我们可以把冲突文件的内容修改成我们需要的内容。
然后使用 git add
和 git commit
重新提交就可以完成合并。
有时候如果冲突的内容过多,可能会存在有些冲突忘了处理就执行的提交操作,这样会让代码再现异常导致编译失败。为了防止这种情况出现,一般推荐在提交(commit)前使用
git diff
命令查看一下有哪些冲突的地方。
删除分支
在需求或 bug 分支合并到主分支以后,我们就不再需要这个分支了。可以使用下面的命令删除指定的分支。
# 删除分支1和分支2
git branch -d 分支1 分支2
# 强制删除分支1和分支2
git branch -D 分支1 分支2
使用 -d
命令可以安全的删除分支,在删除前 git 会检查该分支有没有合并过,如果还没有合并,在使用 -d
参数删除的时候会给出下面这种提示。
error: The branch 'd-from-b2' is not fully merged.
如果一个分支是你本地测试用的分支,并不需要合并到其他任何分支,可以使用大写的 -D
参数来删除分支。
推送新分支
如果本地创建了新分支,在推送更改到服务器时由于本地的新分支无法和服务器端的分支对应,会推送失败。
可以在 push 时使用 --set-upstream
参数指定要推送到的服务器和分支名。
# 用法
git push --set-upstream 服务器名 新分支名
一般默认的服务器名称是
origin
,也可以自行改为其他名称。 比如我的项目一般会有两个远程地址,一个是 Github,名为 github,一个是码云,名为 gitee。如果管理远程服务器, 我们会在进阶操作-远程服务器管理中讲到。