Featured image of post 使用Git进行团队合作

使用Git进行团队合作

使用Git进行团队合作

自上次迁移博客便好久没有更新了,这次更新的内容是关于如何使用Git进行团队合作。这篇文章主要介绍团队合作的工作流程。

基础教程

这篇文章假设你已经对Git有一定的了解,如果你对Git还不太熟悉,建议你先阅读一些关于Git的基础教程,比如廖雪峰的Git教程

此外,还推荐参考以下教程:

  1. Git官方文档:

  2. Pro Git书籍

    • Pro Git是一本免费的电子书,涵盖了 Git 的基础知识和高级用法,非常适合团队合作。

如果你已经对Git有了一定的了解,那么我们就可以开始介绍如何使用Git进行团队合作。

团队合作的工作流程

在GitLab中,仓库成员有5种角色:

  1. Guest(访客):只能查看仓库内容,不能进行写操作;
  2. Reporter(报告者):可以查看和克隆仓库,但不能进行写操作;
  3. Developer(开发者):可以进行写操作,但不能管理项目设置;
  4. Maintainer(维护者):可以管理项目设置和成员;
  5. Owner(所有者):拥有所有权限,包括删除项目。

假设有一个项目,团队成员包括项目经理(1人)、开发者(3人,其中1人中途加入)、测试者(1人)等共5人;

这5人的角色及职责如下:

  1. 项目经理:Owner
  2. 开发者1:Maintainer(负责开发、审核代码)
  3. 开发者2:Developer
  4. 开发者3:Developer(中途加入)
  5. 测试者:Reporter

在开发者3加入之前,项目开发都为团队内部合作开发,代码都应该提交到一个仓库。

在开发者3加入之后,项目涉及到跨团队开发,开发者3应该fork仓库,如果使用GitLab,则通过Merge Request(简称MR)的方式来将代码合并到上游仓库(上游仓库为被fork的仓库);

下面按照GitLab Flow工作流说明。

  1. 项目经理创建仓库,提交需求文档,并添加团队成员为Developer

初次提交如下:

1
2
3
4
5
6
git init .
touch README.md #该文章简要介绍下项目背景及作用
git add README.md   #添加README.md到暂存区
git commit -m "first commit"
git remote add origin https://<host>:<port>/path/to/xxx.git #添加远程库,这里修改成自己的远程库
git push -u origin master

接着提交需求文档:

1
2
mkdir docs  #创建docs目录,在该目录下添加需求文档
touch docs/requirements.md  #采用markdown格式记录需求文档

打开docs/requirements.md文件,完成编写需求文档后操作如下:

1
2
3
git add docs/requirements.md #将需求文档添加到暂存区
git commit -m "[docs] 添加需求文档"
git push origin master
  1. 开发者1提交技术文档,开发者1创建dev分支

提交技术文档操作如下:

1
2
3
git remote add origin https://<host>:<port>/path/to/xxx.git #添加远程库,这里修改成自己的远程库
git fetch origin -a #获取远程库所有分支
touch docs/technical.md #采用markdown格式记录技术文档

打开docs/technical.md文件,完成编写技术文档后操作如下:

1
2
3
4
5
6
git add docs/technical.md   #将技术文档添加到暂存区
git commit -m "[docs]添加技术文档"
git push origin master

git checkout -b dev #创建并切换到dev分支
git push origin dev

团队内部合作

团队内部合作时,开发者1、开发者2、测试者都在同一个团队中,他们都是项目的成员,可以直接提交代码到同一个仓库;

接下来按照GitLab Flow工作流程说明两种应用场景。

开发新功能

开发和测试新功能不能都在一个分支上修改,而应该创建新分支,命名为feat_xxx表示开发xxx功能。

接下来介绍开发者1提Issue并在该Issue中创建2个新分支分别名为feat_xxxtest_xxx,用于开发者2提交功能代码到feat_xxx分支和测试者提交用例代码到test_xxx分支中。

其中,feat_xxx分支是基于最新的dev分支,test_xxx分支是基于feat_xxx分支,工作流如下:

  1. 开发者1按照dev分支的文档提Issue,开发者1在该Issue中基于dev分支创建新分支feat_xxx,并通知开发者2开发该功能

  2. 开发者2获取并checkout到feat_xxx分支

不存在feat_xxx分支则需要创建并切换到新分支,执行以下指令:

1
2
git fetch origin -a #获取远程库所有分支
git checkout -b feat_xxx origin/feat_xxx    #基于远程的feat_xxx分支在本地创建feat_xxx分支,并切换到该分支

已存在feat_xxx分支则执行以下指令:

1
2
git checkout feat_xxx #不需要-base参数,因为已经存在本地feat_xxx分支
git pull origin feat_xxx:feat_xxx #拉取远程feat_xxx分支到本地feat_xxx分支
  1. 开发者2在feat_xxx上进行开发,开发完成后并push到远程库
1
2
3
git add <files>
git commit -m "[feat]添加xxx功能"   #"[feat]"表明这是新增功能
git push origin feat_xxx

注意:修改feat_xxx如果是新增功能,则提交记录的注释应该添加前缀"[feat]"

  1. 开发者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. 开发者1审核代码,审核不通过则在MR中说明改进建议,并回到步骤(3)

这里提交记录的注释仍应该为[perf]改进xxx,这里的"[perf]“为针对xxx做出的改进

  1. 开发者1审核通过则在该Issue中创建子项Issue,基于feat_xxx分支创建test_xxx分支,将该Issue分配给测试者

注意:这里开发者1审核通过不应该通过MR,需要等测试者提交测试用例后并测试通过,然后测试者需要提MR请求将test_xxx分支合并到feat_xxx分支,之后开发者1才能通过确认MR,feat_xxx才被删除;

  1. 测试者拉取测试分支代码,编写测试用例并提交到test_xxx分支
1
2
3
4
5
6
7
#已存在test_xxx分支则执行以下指令
git checkout test_xxx #第一次拉取时执行:git checkout -b test_xxx origin/test_xxx
touch test/test_xxx  #编写测试脚本
#编写脚本可以为其他语言编写的测试用例
git add test/test_xxx
git commit -m "[test]测试xxx功能" #提交记录注释的"[test]",表明它修改了测试代码
git push origin test_xxx

无论测试用例是否正确,都应该提交测试用例程序,且提交记录前缀应该为”[test]",表明这是测试用例。

  1. 测试者此前没有提出MR,则创建MR请求将test_xxx合并到feat_xxx分支中,需将Issue和对应的MR关联

  2. 开发者2审核测试者的MR,不通过则回到步骤(10),否则交由开发者1审核,开发者1审核通过则允许合并

开发者1允许合并将会删除test_xxx分支,然后该MR将被关闭,还需要开发者1标记测试Issue为已完成

  1. 开发者1、开发者2更新feat_xxx,可以先本地测试,若存在问题,则回到步骤(3)
1
2
3
git fetch origin feat_xxx
git checkout feat_xxx       #切换到要合并的本地分支
git merge origin/feat_xxx   #将远程库合并到当前分支
  1. 测试用例都通过则由开发者1确认通过MR,将feat_xxx合并到dev分支中

添加新功能的时序图如下所示:

1

也可以简单的按照如下图理解:

2

处理集成时的问题

单独测试某功能时,该功能一切正常,但在集成时出现问题,便以下面这个示例介绍。

假设需求没有较大变化,原来已有功能feat_xxx1(不存在问题),添加新功能feat_xxx2,单独测试feat_xxx2也不存在问题,但在集成测试后出现了问题,测试者需要先提出Issue,其他成员都应该积极讨论定位问题;

这里不用回到feat_xxx2的分支修复问题(可能feat_xxx1的问题在先前的集成测试中没暴露出来),而是在集成后的dev分支上创建新分支来修复集成问题,

具体工作流如下:

  1. 测试者基于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:

1
2
3
git checkout dev
git tag release-x.x.x   #切换到dev分支,将当前分支创建为一个发布版本
git push origin release-x.x.x   #将该版本推送到远程

一般情况,测试者是在release-x.x.x的分支上测试代码,如果存在问题则在本地创建test-release-x.x.x分支来测试,操作如下:

1
2
3
4
5
git fetch origin -a
git checkout -b test-release-x.x.x origin/release-x.x.x #基于远程发布版本创建测试分支
#测试,如果存在必现的问题则提交对应的测试用例,需要进行以下操作
git commit -m "[test]测试发布版本x.x.x"
git push origin test-release-x.x.x
  1. 在定位问题(假设问题出现在开发者2开发的feat_xxx2)后,则开发者1在Issue中基于存在问题的dev分支创建fix_xxx2分支,并将其指派给开发者2

  2. 开发者2需在fix_xxx2上修改和提交代码

这里的提交记录注释为[fix]修复xxx问题

  1. 开发者2进行和测试者同样的操作,若问题仍浮现则回到步骤(3)

  2. 开发者2提交MR请求将fix_xxx2合并到dev分支中,并将该MR和对应的修复Issue关联起来

  3. 测试者2拉取fix_xxx2进行测试,没有问题后则通过,但不会合并到dev分支

  4. 开发者1观察到bug修复,则最终通过合并,fix_xxx2分支将被删除

跨团队开发工作流

在开发者3加入后,开发者3会因缺乏注意事项导致提交的代码影响到已有功能,因此开发者3应该fork上游仓库来开发,其他成员仍然按照原来的方式开发。

开发者3应该如下操作:

1
2
3
git clone https://<host>:<port>/path/to/repo-xxx.git    #克隆fork的仓库到本地
cd repo-xxx
git remote add upstream https://<host>:<port>/path/to/repo-xxx.git  #添加被fork的仓库为上游仓库

假设项目紧急,本应该由开发者2实现的功能交由开发者3开发,则开发者1应当在上游仓库中提出Issue,基于dev分支创建feat_xxx分支,开发者3收到通知,则应该如下操作:

1
2
git fetch upstream feat_xxx #获取上游仓库的feat_xxx分支
git checkout -b feat_xxx upstream/feat_xxx

此后的操作和团队内开发工作流类似。

注意:

  • 开发者3如果需要提MR,则应当在自己fork的仓库创建MR;

  • 开发者3如果接收到某个指派任务,则应当从上游仓库upstream中拉取对应分支到本地,然后在该分支上修改和提交代码

  • 开发者3不能直接push到上游仓库,而应该push到fork的仓库,然后创建MR请求合并到上游仓库的对应分支,需要在上游仓库中将该MR和对应Issue关联起来。

补充

MR后远程库的分支被删除,如何删除本地fetch的远程库

开发者1确认MR后,远程库的feat_xxx分支被删除,但本地的feat_xxx仍存在

1
2
3
4
git fetch -p    #-p为--prune缩写
git checkout dev    #如果当前在feat_xxx分支上
git branch -d feat_xxx  #删除本地feat_xxx分支
git branch -D feat_xxx  #注意:意味着本地分支没被合并,会被强制删除

某发布版本存在较大问题,如何删除本地存储的已删除远程库tag

1
git fetch -P    #-P为--prune-tags缩写

跨团队合作中,上游仓库MR后,如何将fork的远程库与其上游仓库同步

假设自己MR请求将fix_xxx合并到上游仓库的dev分支,开发者1确认合并后,则操作如下

1
2
3
4
5
6
7
8
git fetch upstream -a   #获取上游仓库的更改
git merge upstream/dev dev  #将上游仓库同步到本地dev分支
#or git pull upstream dev:dev   #代替上面2行指令
git push origin dev     #将本地更改同步到fork仓库中

git checkout dev        #如果当前在fix_xxx分支
git branch -d fix_xxx   #删除本地fix_xxx分支
git fetch -p            #删除本地存储的已删除的fix_xxx分支

如何给新建分支命名

比如,添加新功能添加查询温度功能,应该在当前分支(一般为dev分支)创建名为feat_query_temp的新分支(其中feat为feature的缩写);

新分支命名格式如下:

1
type_subject
  • 提交类型(type): 包括feat(新功能)、fix(修复问题)、doc(文档更改)、refactor(代码重构,不是新增功能,也不是修复bug)、test(添加或修改测试)、chore(其他不修改源码或测试用例的提交)、style(代码格式,不影响代码运行);

  • 简短描述(subject): 使用下划线_分割单词,要求简洁明了.

命名规则示例如下:

1
2
3
4
feat_support_eth    #支持网口
refactor_cmd_resp   #重构指令回复提示信息
style_format_code   #格式化源代码
test_cmd_resp       #测试指令回复提示信息

如何编写提交注释

提交注释尽量做到简介明了,直观反映提交代码的更改;

也应该做到新功能和修复bug的代码应该分为2次来提交,其他类型的提交也应当如此;

可以通过git commit -m <message>指令来提交到本地库,其中<message>的说明如下所示:

1
格式:   [type]msg                

其中,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来执行想要的操作,示例如下:

1
2
3
4
5
6
git clone https://<host>:<port>/path/to/repo_xxx.git
cd repo_xxx.git

touch .git/hooks/pre-commit #在.git/hooks目录下创建pre_commit文本文件
#编辑pre-commit内容
chmod +x .git/hooks/pre_commit  #修改pre-commit属性为可执行

其中pre-commit的内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/sh
# Find all C source files and header files
files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(c|h)$')
# If there are no files to format, exit
[ -z "$files" ] && exit 0
# Run clang-format on each file with the specified .clang-format file
echo "$files" | xargs clang-format -i --style=file
# Add the formatted files back to the staging area
echo "$files" | xargs git add
exit 0

通过上述配置后,每次执行git commit后它会在修改文件中过滤".c"和".h"的文件,然后存在该文件则通过clang-format并按照根目录下的".clang-format"配置文件来格式化该文件。

参考

  1. https://www.cnblogs.com/xiaoqi/p/gitlab-flow.html

  2. https://www.atlassian.com/git/tutorials

  3. https://git-scm.com/book/

  4. https://docs.github.com/zh/get-started/start-your-journey/about-github-and-git

Licensed under CC BY-NC-SA 4.0
最后更新于 Dec 08, 2024 15:41 +0800