Featured image of post 什么才是正确的协作姿势?

什么才是正确的协作姿势?

你真的会用 git 了吗?

前言

为什么我们需要版本管理

版本管理在实际开发中非常重要,进行了版本管理后,项目的每一次变动都有迹可循,谁在什么时候更改了什么内容,都十分清晰明确。其记录大致如下

时间 用户 文件 内容 消息
2022-1-1 21:00 Xav1erSue main.js + import axios from 'axios' 项目引入 Axios
2022-1-1 22:00 Xav1erSue app.vue - console.log(res) 删除console
2022-1-2 06:00 ZhangSan styles.less + transition: all 1s 添加过渡动画

这样一来,如果项目出现问题,不仅可以及时回滚到稳定版本,也可以更好地定位出问题的更新,直接找到变动的内容,从而解决问题。

分布式则代表每个参与协作的开发者本地都有一份完整的版本记录信息,即使远程代码仓库被删除了,只要还有一台电脑上存有这个项目的文件,就可以通过本地的记录进行恢复。

而 git 正是世界上目前最先进的分布式版本管理系统,因此绝大多数协作开发的项目都会使用 git 进行版本控制。

git 基本上有两种方式:

  1. 使用命令行
  2. 使用 IDE 如 WebStorm 等提供的 git 工具

本文主要介绍命令行的操作,这样可以更好地理解 git 的运行机制。

如何使用 Git 和 GitHub 进行团队协作开发

初始化项目

假如你想开个新坑,那么先在 GitHub 上创建一个空仓库,随后 cd 到本地想要上传到仓库的文件夹的根目录,输入:

1
git init

初始化仓库,此时 git 就会开始跟踪这个目录中的所有变动。随后输入:

1
git remote add origin git@github.com:xx/xx.git

与远程主机建立起连接。随后输入:

1
git add .

将所有文件加入到暂存区,这时 git 就会把所有文件的更改记录储存在暂存区,然后你需要输入:

1
git commit -m 'init repo'

来确定一次提交,-m之后的参数本次提交的消息。

确认一次提交后,项目就进行了版本的更新,如果你此时输入

1
git reflog

你就可以看到本地的版本记录了:

1
5a659c6 HEAD@{1}: commit (initial): init repo

这里的5a659c6就是本次提交记录的 ID ,而后面则是之前输入的提交消息。

随后我们需要输入:

1
git push -u origin main

来将本地的更改推送到远程主机,即origin上,这里的参数-u指的是分配一个默认主机,第一次输入之后,后面就不需要再输入了,直接提交即可:

1
git push

协作流程

提交规范

项目通常会使用huskycommitlint对每次提交的 message 进行限制,格式如下:

1
<type>(<scope>): <subject>

其中 type 主要有以下几个类型

  • feat - 新功能 feature
  • fix - 修复 bug
  • docs - 文档注释
  • style - 代码格式(不影响代码运行的变动)
  • refactor - 重构、优化(既不增加新功能,也不是修复 bug)
  • perf - 性能优化
  • test - 增加测试
  • chore - 构建过程或辅助工具的变动
  • revert - 回退
  • build - 打包

那么提交的内容就差不多是这样:

feat: add login button

fix(Form): missing loading status

其中括号是可选的,用来表达修改的范围,这样一来查看提交记录的时候就可以很清楚地查看每一次修改的内容和类型了。

而对于 commit ,最好不要通过一次 commit 提交太多的更改,尽量保持较细的粒度,每次提交就针对一个更改,这样也方便进行版本管理。

分支开发

git 允许一个项目同时存在多个分支,各分支间保持相互独立。

通常,一个团队项目,会有一个main主分支,上面放着生产环境的代码,其次会有一个主dev分支,上面放着开发环境的代码。以杭电助手的Lemon项目为例,main分支下的所有pushmerge操作都会触发CI/CD,代码会自动打包部署到app.hduhelp.com下。

dev分支下的pushmerge操作则会将代码打包上传到appdev.hduhelp.com下,以方便做生产环境的模拟测试。

真正开发时,则需要进行分工,每个人开一个自己的分支,在自己的分支上进行功能开发。例如我从dev分支创建一个名为Xav1erSue/feat-userpage,来在自己的分支上进行开发。如果开发完了,就可以开启一个Pull Requestpr来申请把自己的分支合并到主分支上,以此完成协作开发。

通常情况下我们在提交一个 pr 时,是不能自己 merge 的,往往需要进行 code review ,即找管理员或者项目负责人看看你的提交,然后再通过你的 pr 。而他们可能会根据你的代码提出一些 comments ,你再按照他们的要求进行修改,提交再次申请 review ,直到通过。这样你的分支就可以合并到主分支上,作为项目的正式功能上线啦。当然,在 pr 的过程中通常会做一些 pr check ,即运行 workflow 来对你的提交做一些测试,比如运行单测生成测试报告等,只有通过 check 之后才可以进行 merge。

冲突处理

在协作开发的过程中,最常遇到问题的就是冲突了。比如你还在写代码的时候,主分支上也有人提交了对相同文件的更改,那么这时候你的代码就无法自动 merge 到主分支上了,git 会提示你处理冲突。

又或者是别人在你的分支上提交了代码,导致你无法直接push到自己的分支上,那么这时候应该怎么做呢?

冲突的原因

假设项目分支main分支和dev分支的提交记录如下

main 分支

commit1 -> commit2 -> commit3 -> commit4

dev 分支

commit1 -> commit2 -> commit3 -> commit5 -> commit6

可以看出,在第四次提交中,dev 分支与 main 分支产生了冲突,如果commit4commit5/6没有对相同的地方进行修改,那么此时的 pr 是可以自动处理的,但如果它们修改了相同文件的相同地方,则 pr 无法自动处理, 会提示你需要处理冲突。

分支合并

分支的合并有很多种方式:(点击查看动图)

1. 显式合并( Explicit Merge ),又称非快进合并( non-fast-forward

Explicit Merge

如图所示,其特点在于会将分支上的提交整合成一次提交,合并到主分支上,并且分支信息会被保存。

2. 快进合并( Fast Forward Merge

Fast Forward Merge

如图所示,快进要求开发分支不能落后于主分支,其特点在于合并的时候会将分支的修改直接连接到主分支尾部,看起来和直接在主分支commit一样,记录呈一条直线,且不会保留分支信息

3. 变基( Rebase

Rebase

如图所示,变基结合了快进和非快进,会将开发分支上的提交记录拼接到主分支尾部,同样不会保留分支的信息

4. 压缩( Squash

Squash

如图所示,压缩合并会将分支上的多个提交整合成一个提交后合并,同样不会保留分支信息。

如何选择?

通常main分支上面存放的都是比较稳定的代码,提交频率也很低,而dev分支是用来开发的,上面会存在许多零碎的提交。使用变基合并或者快进合并会把dev上的提交历史原样接入到main中。如果main分支出现了问题需要回滚,你就得分辨哪一次提交是真正的上一次提交,因为如果使用变基合并或者快进合并的话,上一次提交也许仅仅修改了文字的大小而已。

而我们在本地开发的过程中,则是可以使用变基合并或者快进合并的方式同步主分支的代码的,这样可以使分支的提交记录呈一条直线,使得时间穿梭更加方便。

非快进合并是会保留分支信息的,这样一来,即使我们在开发结束后删除了开发分支,项目的主分支提交记录仍然会保留开发分支的记录,而且如果开发分支过多,则整个提交记录可能会出现十几根并行的线,非常不容易维护,因此我们通常只使用压缩合并来进行 pr 的合并,这样可以保证主分支上总体呈现一条直线,更为清晰。

如何处理冲突

第一种方式:直接合并

在这个过程中,你手动将dev分支的内容合并到main分支上

main分支下

1
git merge dev

随后你的编辑器会自动打开有冲突的文件,其中会有这样的标记:

处理冲突

很直观地,上方的就是当前分支冲突部分的代码,下面的就是dev分支冲突部分的代码,将其处理好,把那堆尖括号都删掉,然后输入:

1
2
git add .
git commit -m "xxxx"

来提交这次合并,在 WebStorm 中则可以直观地看到分支时间线的变化。

WebStorm

那么这里实际使用的就是non-fast-forward,即将dev分支下的所有提交合并为一次commit,插入到main分支的末尾,同时会保留分支记录的信息。

第二种方式:变基合并

在这个过程中,我们将 main 分支的代码变基到 dev 分支上,然后再进行快进合并

假设你的分支提交记录如下:

main 分支

init -> commit2 -> commit3

dev 分支

init -> commit2 -> commit4

首先在dev分支下输入:

1
git rebase main

main分支变基到dev分支上,同样处理完冲突后输入:

1
2
git add .
git rebase --continue

确认提交信息之后,再看 git 提交记录,就会发现,你的提交结构变成了这样:

变基完成

可以看出,这个过程中实际上是把main分支下的commit3提交变基到了dev分支上,并且将冲突的修改重新整合成了一次新的commit,连接在了commit3的后面。

那么这样一来,将dev分支合并到main分支就可以使用快速合并进行 merge 了。

切换回main分支,输入:

1
git merge dev

就成功地将dev分支合并到了main分支上,这时候,两个分支的提交记录是一致的,都是一条直线,即使后续删除了这个开发分支,也不会影响main分支上的提交记录。

但是,这里由于进行了变基,所以需要将dev分支强制推送到远程,因此。你需要确保此时没有别人提交你的分支,因此你可以这么操作:

1
git push --force-with-lease

这个参数可以在遇到别人提交的时候拒绝推送,所以可以安全地覆盖远程代码。

后话

现代的 IDE 往往会提供比较完善的 git 工具,但是熟练掌握命令行操作,理解 git 的运行机制,才能更好地实现团队协作。

关于 git 的更多参数,一文肯定难以全部阐述,不过本文所涉及到的功能就足以涵盖日常使用 99% 的场景了,在遇到难以解决的问题时,那就去问问项目管理员或者负责人吧。

CC BY-NC-ND
Built with Hugo
主题 StackJimmy 设计