有效管理 Git 分支与提交
本文档亦在说明工作中使用 Git 和 GitLab 时,进行提交、推送、合并等 Git 操作的原则。从而达到有效管理分支与提交,维护提交历史的目的。
1 合并与变基
Git 分支分为本地分支和远端分支,每个分支之间都存在相互合并和变基的能力。
合并与变基分为在本地进行,和在远端使用合并请求进行。
- 本地操作推荐使用 Visual Studio 进行
- 远端即通过 GitLab 的合并请求进行
1.1 原理
GitLab 的合并请求,分为如下三种方法:
-
默认非快进式合并方法,每次合并都会创建一个合并提交。如下图:
- 目标分支(
destination
)产生了 C 和 D 两个提交 - 源分支(
source
)产生了 K 一个提交,与 C 和 D 的变更没有任何交集 source
合并至destination
,除了提交 K,额外产生一个合并提交 L- 从分支图上能够看出提交 K 是
source
产生的,K 在source
的分支线上,C 和 D 在destination
的分支线上,有一个source
指向destination
的合并历史
%%{init: { 'logLevel': 'debug', 'theme': 'default', 'gitGraph': {'rotateCommitLabel': false, 'mainBranchName': 'destination'}} }%% gitGraph commit id: "A" commit id: "B" branch source checkout destination commit id: "C" commit id: "D" checkout source commit id: "K" checkout destination merge source tag: "L" - 目标分支(
-
快进式合并,不会创建合并提交。如下图:
- 目标分支(
destination
)产生了 C 和 D 两个提交 - 源分支(
source
)产生了 K 一个提交,与 C 和 D 的变更没有任何交集 - 将
source
变基为destination
source
快进式合并至destination
,仅新增一个提交 K- 从分支图中将看不出提交 K 是
source
产生的,C、D、K 都在一条分支线上,观察不到合并历史
%%{init: { 'logLevel': 'debug', 'theme': 'default', 'gitGraph': {'rotateCommitLabel': false, 'mainBranchName': 'destination source'}} }%% gitGraph commit id: "A" commit id: "B" commit id: "C" commit id: "D" commit id: "K" - 目标分支(
-
暂时不对【合并提交与半线性历史记录】进行说明与使用。
1.2 原则
- 为观察提交和合并的历史信息,我们采取默认的合并方法:非快进式的合并提交。
- 为避免提交和合并历史的繁复混乱,我们对合并操作采取如下限制:
- 严格控制从远端新建的分支、本地推送到远端的新分支。若发现分支创建的不适宜,采取相应的记过措施,后续将会根据记过情况跟进惩罚措施。
- 不应无限制地使用合并操作,需遵循下述合并原则。若发现远端产生了不必要的合并提交,采取相应的记过措施。
- 本地应先拉取,再提交或推送,避免无意义的合并提交。任何发生在本地的无意义合并提交,推送前都有补救措施,所以推送一定要谨慎。若发现远端产生了不必要的合并提交,采取相应的记过措施。
- 分支不宜过多,提交不宜过多,部分快速变更、临时修复的分支,合并后应当删除。
- 分支之间不宜来回切换、合并。
- 针对同一功能进行代码开发或缺陷修复时使用了不同的源分支(
source
),向目标分支(destination
)合并时存在冲突,一般不介意将destination
的变更引入source
分支,则可以先将source
分支变基到destination
分支,解决冲突,再进行合并。切记避免将destination
分支合并到source
分支,而应当利用变基操作来进行。- code -> feature
- bug -> develop
- hotfix -> release
- 对于发布类分支的合并操作,在本地完成,无需在 GitLab 新建合并请求来进行合并。原因是合并请求主要目的之一是其对评审工作的高效支持,而此类因发布需求所产生的合并无需评审。另外,源分支领先于目标分支而没有任何落后于目标分支的提交,在本地直接合并时不会产生合并提交,从提交与合并历史可以看出,Visual Studio 对此类合并采取了快进式合并。
- develop -> test
- develop -> release
- 对于主干分支的合并操作,在本地完成,无需在 GitLab 新建合并请求来进行合并。原因是合并请求主要目的之一是其对评审工作的高效支持,而此类主干分支合并无需评审。在本地直接合并,解决合并冲突,同时可能将产生合并提交,Visual Studio 对此类合并采取了非快进式的合并提交。
- feature -> develop
- release -> develop
1.3 操作说明
- 通过 Visual Studio 对本地仓库执行变基的方法,见参考资料【在 Visual Studio 中管理 Git 存储库】。对于源分支提交未推送远端的情况,变基后可直接进行推送。对于源分支提交已推送远端的情况,变基后的推送需要启用强制推送
--force-with-lease
选项。否则仍然需要一次远端源分支到本地的合并。
- 当前不使用 GitLab 合并请求设为快进式合并时,合并请求中所提供的变基功能。
- 一般情况下,推送前均需拉取远端最新的变更到本地,或本地变基到远端,才允许推送。当拉取操作和本地提交顺序颠倒,或在本地提交但未推送远端前,远端又有新的变更,导致拉取时出现一个从远端到本地的合并提交。显然 Visual Studio 对此采取了一个非快进式的合并提交。下述步骤说明如何消解此合并提交:
- (A)产生了一个或多个本地提交(commit)
- (B)此时应尝试提取(fetch)远端分支最新的变更,若远端存在新的提交,前往步骤(C);若远端没有新的提交,前往步骤(D)
- (B-1-1)若此时并非提取而直接执行了拉取(pull),并且远端也有未同步到本地的新变更,本地分支将产生一个远端分支至本地分支的合并提交
- (B-1-2)若已产生合并提交,重置并删除远端拉取下来的更改(reset --hard),包括新产生的合并提交。重置成功后传入、传出、本地历史记录将还原到(B)的状态,此时可以前往步骤(C)
- (B-2)若此时未拉取,直接尝试推送,提示无法推送,请先拉取最新。切记不要执行【拉取然后推送】。不能且无法【强制推送】。选择【取消】。前往步骤(C)
- (C)将本地分支变基(rebase)为远端分支
- (D)将本地分支的提交推送(push)至远端
- (A)产生了一个或多个本地提交(commit)
2 压缩提交
压缩提交的目的是将频繁的微小变动或中间态变更,整合为一个最终形态,减少提交历史中频繁琐碎的小提交个数。
2.1 原则
- 若分支上的提交仍在本地,未推送远端,可以对该分支直接进行压缩。若提交已推送远端,不建议对该分支直接进行压缩。原因是远端的提交历史无法被改写,压缩提交的操作只是将所有提交又额外生成另一个提交,包含在分支当中,只会显得杂乱无章。
- 若提交已推送远端,需要压缩提交时,建议合并时进行压缩,且需要在合并后删除源分支。
- 对于提交已推送远端的分支,压缩提交适用于 Bug、Hotfix、部分 Code 分支,等编写完成后可以随即删除的分支。其不会反复使用,不会与其他分支发生更多的合并(无论作为源分支或目标分支)。否则此分支的提交历史是包含所有小提交的,无法被改写,而目标分支仅包含一个压缩提交。两个分支提交历史对不上,不仅造成不必要的困惑,后续再次发生合并时还会产生不必要的合并冲突,使提交历史更加杂乱无章。
- 压缩提交无法压缩合并提交。
- 适合进行压缩的提交:
- 功能开发前期产生的初始化、代码库搭建提交
- 针对同一功能或 Bug 对同一文件进行的多次调整,如评审意见处理,自己开发过程中产生的纠错变动
- 为解决冲突所产生的提交
2.2 未推送远端的本地提交
未推送远端的本地提交可以在 Visual Studio 的 git 工具内选中所有相关的最近 n
次提交,右键菜单选中【Squash 提交】进行压缩。输入压缩为 1
次提交后的提交消息,点击【Squash】压缩。
2.3 推送至远端的提交
合并时利用压缩提交,如下图勾选【压缩提交】的复选框,可以自动将源分支的合并进行压缩,即使源分支的多次提交已经推送到了远端。同时应确保勾选【删除源分支】选项,以保证提交历史合理有效。不希望删除的源分支,或不具备压缩条件的提交,禁止勾选【压缩提交】。
3 总结
- 本地变更提交前,先拉取远端,获取最新的变更,再进行提交。
- 本地提交推送前,先拉取远端,获取最新的变更,再进行推送。
- 本地分支产生一些提交是正常的,同时也可以结合暂存(add),或储藏(stash),管理变更内容。但不允许出现远端和本地之间的合并提交,需结合重置、变基、快进式合并等操作保持本地提交历史的干净整洁,再进行推送。推送远端的提交历史实施严格把控,禁止出现不符合规范的分支和提交。
- 若不小心先进行了本地提交,后拉取远端,或其他原因导致产生了多余的远端到本地的合并提交。需使用重置并删除更改,消除这些不必要的合并提交。再通过变基来保持本地和远端分支提交历史一致,以便于下一步将本地的提交推送至远端。
- 本地完成的变更提交后,不急于推送。待功能具备一定的形态,形成特定版本,或完成某个议题、里程碑,再通过必要的压缩和变基操作,优化提交结构,最终推送至远端。具体如何优化本地的提交结构,压缩提交、分支变基的操作说明请查看文档前述章节。
- 对于均已推送远端的分支,其分支之间的合并,根据分支的属性、评审需求,采用不同的策略:
source |
destination |
合并 | 解决冲突 |
---|---|---|---|
code | feature | GitLab 合并请求 | Visual Studio 变基 + 强制推送 |
bug | develop | GitLab 合并请求 | Visual Studio 变基 + 强制推送 |
hotfix | release | GitLab 合并请求 | Visual Studio 变基 + 强制推送 |
develop | test | Visual Studio 快进式 | 无 |
develop | release | Visual Studio 快进式 | 无 |
feature | develop | Visual Studio 合并提交 | Visual Studio 合并 |
release | develop | Visual Studio 合并提交 | Visual Studio 合并 |
- 在 GitLab 对合并请求进行合并操作时,根据压缩原则,勾选【压缩提交】的选项进行合并过程中的压缩操作。详见文档第 2 章节。