soda-x / blog

Here is my blog
754 stars 37 forks source link

用工具思路来规范化 git commit message #15

Open soda-x opened 6 years ago

soda-x commented 6 years ago

在团队协作中我们经常碰到的问题是每个人都有自己的开发习惯,这个习惯包含但不限于编码风格,工具使用等,所以往往协作中就会出现各种各样的问题。这篇文章将会从很小的切入点开始讲,即如标题所诉 Git Commit Message

但在讲之前大家最好对如下的分支管理有一定的了解,原因在于,好的分支管理模型和好的 Git Commit Message 是规范化开发必不可少的内容。

衍生阅读 Git 分支管理模型gitflow

为什么要规范 Git Commit Message

在项目开发开发中或许我们能经常看到

相信或多或少大家都曾碰到过。一旦涉及代码回滚,issue 回溯,changelog,语义化版本发布等操作时,作为 PM 肯定一脸懵逼 即使 PM 参与了全程的 CR 环节。

那理想中的 Git Commit Message 应该是要能较好的解决如上问题

而 changelog,语义化版本发布这更像是合理化 commit 后水到渠成之事。

如何算比较好的 Git Commit Message

以个人来看,好的 commit 需要有以下特征

如何写出规范化的 Git Commit Message

当前业界应用的比较广泛的是 Angular Git Commit Guidelines

具体格式为:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

type: 本次 commit 的类型,诸如 bugfix docs style 等 scope: 本次 commit 波及的范围 subject: 简明扼要的阐述下本次 commit 的主旨,在原文中特意强调了几点 1. 使用祈使句,是不是很熟悉又陌生的一个词,来传送门在此 祈使句 2. 首字母不要大写 3. 结尾无需添加标点 body: 同样使用祈使句,在主体内容中我们需要把本次 commit 详细的描述一下,比如此次变更的动机,如需换行,则使用 | footer: 描述下与之关联的 issue 或 break change,详见案例

一方面我们可以通过 commit 模板,但是这对于整体管控而言比较难以把握。所以如标题所诉,我采取了工具化的方式。

问题将会被拆分成

如何利用工具协助 生成 commit

commitizen 来格式化 git commit message 的工具,它提供了一种问询式的方式去获取所需信息,而在一个大框架下,我们肯定有自己想要遵循的范式(即个性化内容,比如 types 的类型),此时就由 commitizen 中的 adapter 来承载,例如如上提到的 angular 规范则是由 cz-conventional-changelog 来实现。

如何利用工具协助 校验 commit

commitlint 来校验 git commit message 的工具,而所需要校验的内容是否符合规范则和 commitizen 一样需要一个 adapter,例如校验 angular 规范的则由 @commitlint/config-conventional 来呈现。

何时校验才算合理

这就需要 husky 了。附所有可用的 hooks

在老版本中在 package.json

"scripts": {
  "commitmsg": "commitlint -e $GIT_PARAMS"
}

在新版本中

  "husky": {
    "hooks": {
      "commit-msg": "commitlint -e $GIT_PARAMS"
    }
  }

水到渠成的 changelog

依托于 commitizen 对 Git Commit Message 的规范化,我们非常容易依托 commit 信息来自动化生成 changelog。

正常情况下,我们可以使用 standard-versionsemantic-release 来生成 changelog。

standard-version 与 semantic-release 的区别,总结来说就是 standard-version 只针对 local git repo 而 semantic-release 则会牵扯到代码 push 亦或 npm publish。

另外如果你的项目是 mono repo 的,即通过 lerna 来管理的,然后代码又托管在 github 上,那么 lerna 也给了一套自己的解决方案,一种基于 github tag 给 pr 和 issue 打标的方式。这一块可以见我之前的文章 monorepo 新浪潮 | introduce lerna

项目实战

在实际我们的业务项目中当前有两种场景,一种是普通的 repo,还有一种是 mono repo,如果还不知道 mono repo 是什么的,可以参考下我之前写的这篇文章 monorepo 新浪潮 | introduce lerna,这篇文章也在文章上面有所提到。

普通 repo

为了让读者可以快速上手,我已经把相关内容整理到一个示例 repo - normal repo,对它的解释是 Starter kit with zero-config for building a library in ES6, featuring Prettier, Semantic Release, and more! 这是一个还在进行中的 repo,当前还确少 babel 那个部分,如果是 ts 用户的话 还需要 ts 那个部分,这些都是需要后续补上的部分。

接下来说下关键部分,先上 package.json

 "scripts": {
  "ct": "git-cz",
  "precommit": "lint-staged",
  "commitmsg": "commitlint -e $GIT_PARAMS",
  "release": "standard-version"
},
"config": {
  "commitizen": {
    "path": "./node_modules/cz-conventional-changelog"
  }
},
"standard-version": {
  "skip": {
    "commit": true,
    "tag": true
  }
},
"lint-staged": {
  "*.js": [
    "prettier --trailing-comma es5 --single-quote --write",
    "git add"
  ]
},

在常规开发中,我们的操作方式会变更为如下:

第一步:使用 commitizen 替代 git commit

使用

$ npm run ct

来替代原有的 git commit

如果你把 commitizen 安装在全局,即 -g

那么也可以使用

$ git ct

来替代原有的 git commit

如果你是 sourceTree 用户,其实也不用担心,你完全可以可视化操作完后,再在命令行里面执行 ct 命令,这一部分确实破坏了整体的体验,当前并没有找到更好的方式来解决。

第二步:格式化代码

这一步,并不需要人为干预,因为 precommit 中的 lint-staged 会自动化格式,以保证代码风格尽量一致

第三步:commit message 校验

这一步,同样也不需要人为介入,因为 commitmsg 中的 commitlint 会自动校验 msg 的规范

第四步:当有发布需求时

使用

$ npm run release

在这一步中,我们依托 standard-version 的能力,输出 changelog,细心的同学可以看到在配置 standard-version 时,我们忽略了相关的打标操作。 原因在于,我们会介入修改 changelog,因为依托 commit msg 的 changelog 对用户而言或许并不直观。 如果没有这种特殊需求的,可以选择打标。

"standard-version": {
  "skip": {
    "commit": true,
    "tag": true
  }
},

第五步:发布

$ npm publish

mono repo

同上,这是一个使用 mono repo 的快速上手示例。 对它的解释是 Starter kit with lerna and zero-config for building a library in ES6, featuring Prettier, Semantic Release, and more!

mono repo 最大的差异是,需要用不同的 commiizen adapter 来适配 mono repo 这种特殊的项目结构,所以在这边我们也选用了 cz-lerna-changelog,最大原因在于我们想要根据 commit 生成 changelog 时 commit 能落实到对应的 package,以及有一份归总的 changelog,这份 changelog 能说明所有的子 packages 的 changelog。

同样说下关键部分,先上 package.json

"scripts": {
  "ct": "git-cz",
  "precommit": "lint-staged",
  "commitmsg": "commitlint -e $GIT_PARAMS",
  "release": "lerna publish --conventional-commits --skip-git --skip-npm",
  "publish": "./tasks/publish.js"
},
"config": {
  "commitizen": {
    "path": "./node_modules/cz-lerna-changelog"
  }
},
"lint-staged": {
  "*.js": [
    "prettier --trailing-comma es5 --single-quote --write",
    "git add"
  ]
},

这边我只说下差异部分

第四步:当有发布需求时

使用

$ npm run release

在这一步中我们借助了 lerna 自身的能力来根据 commit msg 来生成了 changelog,同样我们忽略了打标,以及发布流程,原因依旧是我们需要修改自动化生成的 changelog。但这个操作带来的问题是后续需要手动进行 publish 的操作。在实际业务项目里面,我们的选择是在项目根目录中新建一个 tasks 目录,该目录内放一些自动化脚本,比如这里有 publish.js

所以这边变成利用 release 来生成 changelog,继而我们修改,然后再到根目录中执行 npm run publish 来执行 tasks/publish.js。这部分当前我还没有同步到示例中,后续会添加。

第五步:发布

$ npm run publish

缘由如上诉。

总结

前几天听 UCAN 分享,温伯华提到一点特别印象深刻,大意就是成长必定是上坡路,必定是艰辛的,其实没有如上工具照样可以开发,然而它的存在是让人养成良好的协作习惯,而好的习惯是可以让人受益终身的,所以希望作为读者的你,能踏上这个上坡路。

Ref: https://github.com/angular/angular.js/blob/master/DEVELOPERS.md

huyansheng3 commented 6 years ago

前几周给项目了加个这个,leader 说没必要。。。团队总共就4个人,加了还麻烦。。

soda-x commented 6 years ago

@huyansheng3 应该坚持一下 XD

一方面可以让团队协作更规范和顺畅些,另外一方面也是培养下良好的开发习惯,这是让人受益匪浅的地方。

代码实现 架构 可以张扬个性,但最基本的诸如代码规范,提交日志还是需要统一化。

其实我是更看中一旦规范化,对于 issue 的溯源,CR,和后续自动化 changelog 都有着比较重要的意义。

soda-x commented 6 years ago

done


action

superman66 commented 6 years ago

同感。在没有使用 commitizen 之前,commit message 都是写得很随意。即不利于团队开发也不方便查看,很容易出现一些无意义的提交信息。 后面公司项目和个人项目都用上了,整个 commit message 看起来就规范且清晰多了。

coolme200 commented 6 years ago

体验上可以参考 https://github.com/tj/git-extras 的做法。

git cmtnpm run cmt 这种 体感会好很多

soda-x commented 6 years ago

@coolme200 嗯,其实把 commitizen 装在全局,全局就可以使用 git ct

我把这个部分同步到文章内。

huyansheng3 commented 6 years ago

@pigcan 嗯,分支还保留着。。虽然没有加上去,但自己每次提交的时候都会按上面的规范来。如果改变不了别人,就从改变自己开始吧。

carvinlo commented 6 years ago

为啥用git ct,空注释也能提交,好像没经过校验

DiamondYuan commented 5 years ago

我是 commitizen 装在里项目里,然后 yarn commit 来提交 commit message。 然后利用 hook 在 commit 后执行 lint 和 test 。如果不通过则自动取消 commit。

AlexZhong22c commented 5 years ago

standard-version可以尝试,但是的确是不太好用。

  1. 像一般项目,可能要几年时间才能发3个/4个大版本,使用这个工具的频率不高。

  2. 像是次版本的频率,再加上这个工具不是太智能,也不太可能会用这个工具。

  3. standard-version所做的工作不多,也可以被其他工具(生成changelog之类的工具)代替;其余部分还是由人工来完成关键的步骤比较踏实一点(人工取代standard-version的工作量也不麻烦啊)

xuxusheng commented 5 years ago

学习了

CBCzed commented 5 years ago

为啥我使用standard-version -r 1.0.0 -p beta来指定版本号会不生效呢?