惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
T
The Blog of Author Tim Ferriss
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
Engineering at Meta
Engineering at Meta
量子位
I
InfoQ
Jina AI
Jina AI
Microsoft Security Blog
Microsoft Security Blog
H
Help Net Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
G
Google Developers Blog
J
Java Code Geeks
Recent Announcements
Recent Announcements
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
V
V2EX
腾讯CDC
P
Proofpoint News Feed
A
About on SuperTechFans
爱范儿
爱范儿
U
Unit 42
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Last Week in AI
Last Week in AI

Loong's Blog

长春之行 记录2025年的国庆 长期规划 异地组网联机游戏 对2024年的总结 使用DDNS访问内网设备 开发环境管理工具 关于 博客迁移 合肥两个月生活体验 黑群晖使用体验 通过Jenkins自动化构建 破解光猫超级用户密码 归档 日记 工具 开发 链接 实践 搜索 随笔 团队合作 网络 折腾
使用Git进行团队合作
2024-10-06 · via Loong's Blog

Featured image of post 使用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的缩写);

新分支命名格式如下:

  • 提交类型(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>的说明如下所示:

其中,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