使用Git进行团队合作
自上次迁移博客便好久没有更新了,这次更新的内容是关于如何使用Git进行团队合作。这篇文章主要介绍团队合作的工作流程。
基础教程
这篇文章假设你已经对Git有一定的了解,如果你对Git还不太熟悉,建议你先阅读一些关于Git的基础教程,比如廖雪峰的Git教程。
此外,还推荐参考以下教程:
如果你已经对Git有了一定的了解,那么我们就可以开始介绍如何使用Git进行团队合作。
团队合作的工作流程
在GitLab中,仓库成员有5种角色:
- Guest(访客):只能查看仓库内容,不能进行写操作;
- Reporter(报告者):可以查看和克隆仓库,但不能进行写操作;
- Developer(开发者):可以进行写操作,但不能管理项目设置;
- Maintainer(维护者):可以管理项目设置和成员;
- Owner(所有者):拥有所有权限,包括删除项目。
假设有一个项目,团队成员包括项目经理(1人)、开发者(3人,其中1人中途加入)、测试者(1人)等共5人;
这5人的角色及职责如下:
- 项目经理:Owner
- 开发者1:Maintainer(负责开发、审核代码)
- 开发者2:Developer
- 开发者3:Developer(中途加入)
- 测试者:Reporter
在开发者3加入之前,项目开发都为团队内部合作开发,代码都应该提交到一个仓库。
在开发者3加入之后,项目涉及到跨团队开发,开发者3应该fork仓库,如果使用GitLab,则通过Merge Request(简称MR)的方式来将代码合并到上游仓库(上游仓库为被fork的仓库);
下面按照GitLab Flow工作流说明。
- 项目经理创建仓库,提交需求文档,并添加团队成员为Developer
初次提交如下:
|
|
接着提交需求文档:
|
|
打开docs/requirements.md
文件,完成编写需求文档后操作如下:
|
|
- 开发者1提交技术文档,开发者1创建dev分支
提交技术文档操作如下:
|
|
打开docs/technical.md
文件,完成编写技术文档后操作如下:
|
|
团队内部合作
团队内部合作时,开发者1、开发者2、测试者都在同一个团队中,他们都是项目的成员,可以直接提交代码到同一个仓库;
接下来按照GitLab Flow
工作流程说明两种应用场景。
开发新功能
开发和测试新功能不能都在一个分支上修改,而应该创建新分支,命名为feat_xxx
表示开发xxx功能。
接下来介绍开发者1提Issue并在该Issue中创建2个新分支分别名为feat_xxx
和test_xxx
,用于开发者2提交功能代码到feat_xxx
分支和测试者提交用例代码到test_xxx
分支中。
其中,feat_xxx
分支是基于最新的dev
分支,test_xxx
分支是基于feat_xxx
分支,工作流如下:
开发者1按照dev分支的文档提Issue,开发者1在该Issue中基于
dev
分支创建新分支feat_xxx
,并通知开发者2开发该功能开发者2获取并checkout到
feat_xxx
分支
不存在feat_xxx
分支则需要创建并切换到新分支,执行以下指令:
|
|
已存在feat_xxx
分支则执行以下指令:
|
|
- 开发者2在
feat_xxx
上进行开发,开发完成后并push到远程库
|
|
注意:修改feat_xxx如果是新增功能,则提交记录的注释应该添加前缀"[feat]"
- 开发者2此前没有提MR,则创建MR请求将
feat_xxx
合并到dev
分支,需将Issue和该MR关联起来
其中在开发者2需要在完成新功能后,需在Issue中关联MR,通过!<MR_number>
的方式,假设该MR的序号为9,则应该在Issue中添加注释See merge request !9
;
同样在Merge Request中添加注释表明这是对应哪个Issue,通过#<Issue_number
的方式,假设该MR对应的Issue为5,则添加注释See issue #5
。
- 开发者1审核代码,审核不通过则在MR中说明改进建议,并回到步骤(3)
这里提交记录的注释仍应该为[perf]改进xxx
,这里的"[perf]“为针对xxx做出的改进
- 开发者1审核通过则在该Issue中创建子项Issue,基于
feat_xxx
分支创建test_xxx
分支,将该Issue分配给测试者
注意:这里开发者1审核通过不应该通过MR,需要等测试者提交测试用例后并测试通过,然后测试者需要提MR请求将test_xxx
分支合并到feat_xxx
分支,之后开发者1才能通过确认MR,feat_xxx
才被删除;
- 测试者拉取测试分支代码,编写测试用例并提交到
test_xxx
分支
|
|
无论测试用例是否正确,都应该提交测试用例程序,且提交记录前缀应该为”[test]",表明这是测试用例。
测试者此前没有提出MR,则创建MR请求将
test_xxx
合并到feat_xxx
分支中,需将Issue和对应的MR关联开发者2审核测试者的MR,不通过则回到步骤(10),否则交由开发者1审核,开发者1审核通过则允许合并
开发者1允许合并将会删除test_xxx
分支,然后该MR将被关闭,还需要开发者1标记测试Issue为已完成
- 开发者1、开发者2更新
feat_xxx
,可以先本地测试,若存在问题,则回到步骤(3)
|
|
- 测试用例都通过则由开发者1确认通过MR,将
feat_xxx
合并到dev
分支中
添加新功能的时序图如下所示:
也可以简单的按照如下图理解:
处理集成时的问题
单独测试某功能时,该功能一切正常,但在集成时出现问题,便以下面这个示例介绍。
假设需求没有较大变化,原来已有功能feat_xxx1(不存在问题),添加新功能feat_xxx2,单独测试feat_xxx2也不存在问题,但在集成测试后出现了问题,测试者需要先提出Issue,其他成员都应该积极讨论定位问题;
这里不用回到feat_xxx2的分支修复问题(可能feat_xxx1的问题在先前的集成测试中没暴露出来),而是在集成后的dev
分支上创建新分支来修复集成问题,
具体工作流如下:
- 测试者基于
dev
分支进行测试,测出问题则提Issue,说明问题的现象和执行了什么操作
如果该问题是在某次测试用例中出现,则应该提交测试用例程序然后提交MR请求将test_dev
合并到dev
分支,然后在Issue中关联MR和对应Issue
如果其他成员讨论确认存在问题,则在开发者1、开发者2通过测试者的MR后,由开发者1确认合并,test_dev
分支将被删除
注意:
可能同时在开发feat_xxx3和feat_xxx4功能,每个功能单独测试都没问题,但在集成后出现问题,它们恰好没有冲突地被合并到dev
分支,测试者应当在本地新建两个新分支针对这两次集成,针对feat_xxx3合并到dev的提交,则创建test_dev
分支,针对feat_xxx4合并到dev的提交则创建test_dev1
分支,分支命名依次类推。
如果连续多次合并到dev
分支,包括添加新功能、修复旧功能的问题,则应该通过如下指令创建tag:
|
|
一般情况,测试者是在release-x.x.x
的分支上测试代码,如果存在问题则在本地创建test-release-x.x.x
分支来测试,操作如下:
|
|
在定位问题(假设问题出现在开发者2开发的feat_xxx2)后,则开发者1在Issue中基于存在问题的
dev
分支创建fix_xxx2
分支,并将其指派给开发者2开发者2需在
fix_xxx2
上修改和提交代码
这里的提交记录注释为[fix]修复xxx问题
开发者2进行和测试者同样的操作,若问题仍浮现则回到步骤(3)
开发者2提交MR请求将
fix_xxx2
合并到dev
分支中,并将该MR和对应的修复Issue关联起来测试者2拉取
fix_xxx2
进行测试,没有问题后则通过,但不会合并到dev
分支开发者1观察到bug修复,则最终通过合并,
fix_xxx2
分支将被删除
跨团队开发工作流
在开发者3加入后,开发者3会因缺乏注意事项导致提交的代码影响到已有功能,因此开发者3应该fork上游仓库来开发,其他成员仍然按照原来的方式开发。
开发者3应该如下操作:
|
|
假设项目紧急,本应该由开发者2实现的功能交由开发者3开发,则开发者1应当在上游仓库中提出Issue,基于dev
分支创建feat_xxx
分支,开发者3收到通知,则应该如下操作:
|
|
此后的操作和团队内开发工作流类似。
注意:
开发者3如果需要提MR,则应当在自己fork的仓库创建MR;
开发者3如果接收到某个指派任务,则应当从上游仓库
upstream
中拉取对应分支到本地,然后在该分支上修改和提交代码开发者3不能直接push到上游仓库,而应该push到fork的仓库,然后创建MR请求合并到上游仓库的对应分支,需要在上游仓库中将该MR和对应Issue关联起来。
补充
MR后远程库的分支被删除,如何删除本地fetch的远程库
开发者1确认MR后,远程库的feat_xxx
分支被删除,但本地的feat_xxx
仍存在
|
|
某发布版本存在较大问题,如何删除本地存储的已删除远程库tag
|
|
跨团队合作中,上游仓库MR后,如何将fork的远程库与其上游仓库同步
假设自己MR请求将fix_xxx
合并到上游仓库的dev
分支,开发者1确认合并后,则操作如下
|
|
如何给新建分支命名
比如,添加新功能添加查询温度功能,应该在当前分支(一般为dev分支)创建名为feat_query_temp
的新分支(其中feat为feature的缩写);
新分支命名格式如下:
|
|
提交类型(type): 包括
feat
(新功能)、fix
(修复问题)、doc
(文档更改)、refactor
(代码重构,不是新增功能,也不是修复bug)、test
(添加或修改测试)、chore
(其他不修改源码或测试用例的提交)、style
(代码格式,不影响代码运行);简短描述(subject): 使用下划线
_
分割单词,要求简洁明了.
命名规则示例如下:
|
|
如何编写提交注释
提交注释尽量做到简介明了,直观反映提交代码的更改;
也应该做到新功能和修复bug的代码应该分为2次来提交,其他类型的提交也应当如此;
可以通过git commit -m <message>
指令来提交到本地库,其中<message>
的说明如下所示:
|
|
其中,type取值及其含义如下:
type | 含义 | 示例 |
---|---|---|
feat | 新功能(feature) | [feat]添加健康检查 |
fix | 修复问题(fix bug) | [fix]修复健康检查归位问题 |
doc | 文档更改(documentation) | [doc]完善技术文档 |
style | 代码格式化 | [style]格式化非生成的源码 |
refactor | 重构代码 | [refactor]重构指令回复提示 |
test | 添加或修改测试 | [test]测试健康检查 |
chore | 不修改源码或测试文件的提交 | [chore]完善.gitignore |
perf | 提高性能的更改 | [perf]提高滑动平均性能 |
ci | 持续集成相关的更改 | [ci]能自动生成并发布hex和bin文件 |
build | 构建系统或外部依赖的更改 | [build]完善CMakelists.xt配置 |
revert | 撤销之前的提交 | [revert]回退定时任务 |
每次提交如何自动执行特定操作
假如每次提交前都要格式化代码,正常情况下应该通过主动进行特定操作,但可通过配置.git/hooks/pre-commit
来执行想要的操作,示例如下:
|
|
其中pre-commit的内容如下:
|
|
通过上述配置后,每次执行git commit
后它会在修改文件中过滤".c"和".h"的文件,然后存在该文件则通过clang-format
并按照根目录下的".clang-format"配置文件来格式化该文件。