Hook Git实现代码与需求的一致性

作者:闲鱼技术-皓黯

背景

​ 大多数团队有一套需求缺陷的管理方案,也有一套代码管理方案,但两者常常并无关联或关联成本较高。这导致了测试同学无法通过需求缺陷管理平台跟踪开发同学修复该缺陷的代码,而开发同学也难以根据前人的代码提交记录去了解这段代码对应的需求或缺陷。如果我们能将需求、缺陷与代码进行关联,便能使项目管理和团队协作变得更有效率。

Hook Git实现代码与需求的一致性

目标

​ 写一段代码通常意味着实现一个需求或修复一个缺陷,将代码与需求或缺陷关联对软件生命周期最长的维护阶段来说是一件非常有意义的事。代码与需求的关联可以使开发同学更容易理解代码的意图,降低软件维护成本;代码与缺陷的关联则能让测试同学更容易追踪管理缺陷,提高软件稳定性。而对于自动化测试项目来说,可以通过代码提交记录中的需求或缺陷ID自动选择相关的测试用例执行,使得自动化测试项目更加高效。

​ 为了表述方便,下文我们将需求和缺陷统称为工作项。每个工作项都有一个对应的ID,我们只要将该ID填入每次代码提交时的commit message中,便能有效将代码与工作项关联起来。

举个例子:

​ 我们平常的commit message都是以下这样子:

完成a业务

​ 假设a业务对应的工作项ID为12345678,那么我们希望这条commit message变成如下格式:

*12345678  完成a业务

http://www.example.com/task/12345678

​ 其中*表示这个工作项是一个需求,如果是工作项是缺陷的话,我们可以用#表示。底部的链接是工作项管理平台对应的链接。

​ 相比原始的commit message,我们需要加上一个工作项ID,以及在其底部追加一个对应的链接。那么我们该如何实现呢?

方案演进

1 纯手动关联

​ 这是最容易想到的一个方案,我们每次commit时,message是自己填写的,那么我们只要按照规范,手动将工作项ID填入commit message,再在尾部追加链接即可。实际上,目前有些团队确实是使用该方式关联代码与工作项的。该方案的流程图如下:

Hook Git实现代码与需求的一致性

该方案的缺点十分明显:

操作繁琐

​ 采用该方案后,开发同学每次提交代码,需要到工作项管理平台,找到对应的工作项ID以及链接,将它们复制粘贴到需要提交的message中,这极大改变了他们平时的习惯。

强制性差

​ 该方案并没有检测commit message是否符合规范的机制,在操作繁琐的情况下,使用同学一定少之后少,最后导致方案无法落地。

如果团队一开始就采用这个方案或许问题不大,但是想让团队中途采用这个方案,推广落地难度极大。

2 辅助手动关联

​ 辅助手动关联方案是在纯手动关联方案上,对后者遇到的两个问题做了改进。

简化操作

​ 对开发同学来说,每次提交代码前都需到工作项管理平台查询工作项ID是一件非常繁琐的事,那么是否有其他更为快捷的方式呢?

​ 由于绝大部分需求缺陷管理平台会提供相应的api,所以我们完全可以自己做一个命令行工具,用于查询指派给“我”的需求或者缺陷。我们给这个命令行工具起名为fish。当在终端执行fish tasks命令时,会通过http请求获取“我”名下的工作项,并打印出来。

​ 命令行工具 fish使用何种技术实现并无强制要求,完全可以用自己最熟悉的技术和语言去实现它。当然,如果对这方面没有什么经验的话,我们比较推荐Node.js相关技术。

​ 有了这个工具之后,开发同学再也不用去工作项管理平台寻找ID了,可以在终端上完成整个关联流程了。

强制检查

​ 在commit message不符合我们制定的规范时,我们可以使用git hooks去终止本次commit。下面我们简单学习一下git hooks相关知识:

​ 和其它版本控制系统一样,Git 能在特定的重要动作发生时触发自定义脚本。 有两组这样的钩子:客户端的和服务器端的。 客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。 你可以随心所欲地运用这些钩子。

​ 钩子都被存储在 Git 目录下的 hooks 子目录中。 也即绝大部分项目中的 .git/hooks 。 当你用 git init 初始化一个新版本库时,Git 默认会在这个目录中放置一些示例脚本。这些脚本除了本身可以被调用外,它们还透露了被触发时所传入的参数。 所有的示例都是 shell 脚本,其中一些还混杂了 Perl 代码,不过,任何正确命名的可执行脚本都可以正常使用 —— 你可以用 Ruby 或 Python,或其它语言编写它们。 这些示例的名字都是以 .sample 结尾,如果你想启用它们,得先移除这个后缀。

​ 把一个正确命名且可执行的文件放入 Git 目录下的 hooks 子目录中,即可激活该钩子脚本。 这样一来,它就能被 Git 调用。

​ 那么我们只要使用正确的客户端钩子,在开发同学commit时,获取其提交的commit message,判断是否符合规范,不符合规范则终止提交,那么有没有这么一种客户端钩子呢,commit-msg钩子能满足我们的要求:

​ commit-msg 钩子接收一个参数,此参数即上文提到的,存有当前提交信息的临时文件的路径。 如果该钩子脚本以非零值退出,Git 将放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。

​ 当然,commit-msg的作用不止于此,它除了可以校验commit message是否正确以外,还能根据一定规则修改commit message。在纯手动关联方案中,我们每次要将链接复制粘贴到commit message中,而通过commit-msg,我们完全可以根据commit message中只填入工作项ID,而commit-msg钩子会根据commit message中的工作项ID生成对应的链接,添加到commit message的尾部。

至此,纯手动关联方案遇到的两个问题得到了有效的解决,流程也变成了如下样子:

Hook Git实现代码与需求的一致性

​ 当然,这里遗留了一个问题:

需要注意的是,克隆某个版本库时,它的客户端钩子并不随同复制。

​ 也就是说如何让团队其他同学的客户端钩子生效,是一个棘手的问题,后续我们会详细讲解该问题的解决方案。

3 选择关联

​ 辅助关联方案有效解决了操作繁琐与强制性差两个问题,但是我们是不是还能继续优化,做得更好呢?

​ 再次简化操作:在辅助关联方案中,我们成功把通过平台查询ID变成了通过命令行查询ID,但是每次提交代码还是需要 查询ID -> 复制ID -> 粘贴ID -> 填写message -> 提交代码

​ 那么如果我们能将操作简化为:选择ID -> 填写message -> 提交代码或者填写message -> 选择ID -> 提交代码 那将极大简化开发同学操作。

3.1 命令行

​ 既然我们已经能通过命令行工具做到查询工作项ID,那么我们是不是可以用命令行工具来提交代码呢?答案是肯定的。我们给命令行工具增加了一个git ci命令用于代替git commit。当在终端输入git ci,会出现工作项ID选择的交互。

Hook Git实现代码与需求的一致性

​ 当用户选择了工作项ID并填写了message后,会生成最终的commit message,并调用git commit提交代码。

Hook Git实现代码与需求的一致性

​ 命令行选择关联方案让开发同学不再需要复制粘贴ID,在操作上比辅助关联方案更为简洁轻便。该方案的流程图如下:

Hook Git实现代码与需求的一致性

3.2 Git可视化工具

​ 命令行选择工作项ID的关联方案并不适用于Sourcetree等Git可视化工具,我们需要给平常使用Git可视化工具提交代码的同学提供一套可替代的方案。当开发同学点击Git可视化工具上的"提交"按钮的一瞬间,实际上也是执行了git commit命令,那么也会走commit-msg钩子的逻辑,因此我们可以在commit-msg钩子做校验之前,弹窗提示用户选择工作项ID。

Hook Git实现代码与需求的一致性

Git可视化工具关联方案的流程图如下:

Hook Git实现代码与需求的一致性

4 分支名关联

​ 选择关联方案实际上已经足够轻量了,代码提交和原先并没有什么不同,只是多了一步ID选择罢了。那么我们是不是在一些特定情况下,将ID选择的步骤也可以省略呢?

​ 假设我们在开发需求的过程中,分支名是符合以下规范的

task_{task_id}_suffix

​ 比如在开发某期A业务时,我们的分支名为task_12345678_a,那么我们完全可以利用客户端钩子commit-msg来获取分支名称中的ID,并将其写入commit message中。只要分支名符合规范,那么无论是使用命令行还是Sourcetree,连选择ID的步骤都可以省去,就完成了关联。

Hook Git实现代码与需求的一致性

​ 这个方案中唯一比较棘手的就是切分支时要按照这个规范,这块的优化也非常简单,因为我们之前选择关联方案中提供了git ci命令来选择ID提交代码,所以这里我们完全可以提供一个git co命令来选择ID切一个符合命令规范的分支。

客户端钩子生成及生效问题

​ 前文我们多次提到了客户端钩子,那么这个客户端钩子如何生成,又如何生效呢。

1 客户端钩子生成

​ 我们的客户端钩子默认路径,是git项目下的.git/hooks,这个目录并不在git的管理范围内,所以在这个目录下添加的文件,比如我们上文提到的commit-msg,是无法共享给团队其他同学的。

​ 好在git 2.9之后,我们可以修改客户端钩子路径,所以我们可以在git项目下新建一个名为.githooks的文件夹,并将客户端钩子文件放在这个文件夹下。并调用以下命令让其生效

fish config core.hooksPath .githooks/

​ 当然如果我们对每个工程都手动去创建一遍.githooks目录并添加钩子,是一件非常吃力的事。因此我们需要用命令行工具来解决这个问题。当执行命令fish hook install时,会自动在git目录下创建.githooks,并生成客户端钩子。

2 客户端钩子手动生效

​ 虽然.githooks目录在git的管理范围内,团队同学可以共享这个文件,但是如果他们不执行git config core.hooksPath .githooks/命令的话,客户端钩子还是无法生效的。为了方便团队同学记忆,我们提供了一个fish hook enable命令用于客户端钩子生效。

3 客户端钩子自动生效

3.1 case by case

​ 客户端钩子手动生效方案的设想是团队每个同学都记得在克隆项目后手动调用一遍fish hook enable。理想很丰满,现实很骨感。事实上,并没有几个同学会记得这一点。所以我们需要一个能让客户端钩子自动生效的方案。

​ 首先,我们想到了一个case by case的方案。我们先写了一段shell脚本用于githooks生效。之后,分别在Android工程的build.gradle,iOS工程的build phase以及前端工程的post install加入该代码。这个方案能解决一部分问题,但是并不具有普遍性。

3.2 替换git

​ 那么有没有什么一劳永逸的方案呢。git客户端钩子需要手动生效这个机制是无法修改的,我们不可能要去为了这个小功能修改git源码然后让大家装上。修改git源码代价太大,但是给git套上一层外壳倒不是什么难事。

​ 之前我们的命令行工具提供了提交代码、切分支以及生效客户端钩子的各种命令,所以开发同学每个人都需要安装这个命令行工具。我们完全可以在开发同学安装该工具时加入一段代码,用于执行执行git替换命令,将真实的git改名为gitreal,再生成一个名为git的shell脚本。shell脚本中的所有命令最后都会交给gitreal执行,起到了一层代理的作用。如果用户在执行git commit命令时,shell脚本会先判断该git工程是否有.githooks文件夹,如果有,则执行客户端钩子生效的逻辑。

​ 至此,我们再也无需担心客户端钩子生效的问题了,只要git工程中的.githooks文件的客户端钩子存在,那么团队其他同学就会自然生效。

知识拓展​

git hooks

​ git hooks的使用场景非常多,由于篇幅有限,我们前面只介绍了客户端钩子中的commit-msg钩子,如果想系统学习这方面的知识,可以读一读git的官方文档《Customizing-Git-Git-Hooks》。

Node.js

​ 如果你对实现一个命令行工具感到无从下手,可以参考一下文章《interactive-command-line-application-node-js》,相信你很快就能实现一个属于自己的命令行工具。当然,为了能更快更好实现它,你很可能需要用到以下组件:

commander : 用于简化命令行工具开发

Inquirer.js : 致力于交互式命令开发

shelljs:执行shell脚本更为便捷

chalk :为终端输出增添色彩

总结

​ 代码与工作项的关联方案,使用了较低的成本,有效提升了团队协同工作的效率。当然,这只是闲鱼团队高效协作方案的一小部分。在闲鱼,我们都是这么工作的,我们推崇无人化的方式解决问题,如果你也是一个对技术有追求的同学,欢迎加入我们。

​ 最后,闲鱼技术团队广招各类方向的达人,无论你是精通移动端,前端,后台,还是机器学习,音视频,自动化测试等,都欢迎投递简历加入我们,一同用技术改善生活!

简历投递:guicai.gxy@alibaba-inc.com

参考资料

https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

https://www.smashingmagazine.com/2017/03/interactive-command-line-application-node-js/

https://github.com/tj/commander.js

https://github.com/sboudrias/Inquirer.js

https://github.com/shelljs/shelljs

https://github.com/chalk/chalk

上一篇:多表连接索引的问题


下一篇:block层IO调度器 (deadline调度算法) linux内核源码详解