Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

开发者必看!在团队中我是这样实现 Git 提交规范化的 #5

Open
QFifteen opened this issue Dec 9, 2024 · 0 comments
Labels
通俗易懂的中后台系统建设指南 该系列旨在告诉你如何来构建一个优秀的中后台管理系统

Comments

@QFifteen
Copy link
Owner

QFifteen commented Dec 9, 2024

前言

本篇文章主要讲解如何从零搭建一套 Git 提交规范化流程,主要分为 lint 校验和 Commit Message 规范化

在团队合作中,Git 提交如果没有规范化,会让代码库变得混乱不堪,影响协作效率,特别是在多人协作中。如何从零开始搭建一套高效的 Git 提交规范化流程,是每个团队都需要思考的问题

本篇文章结构紧凑,因果关系明确,最好从头读到尾,跟着逻辑思路走,不建议中途跳段

本文也是《通俗易懂的中后台系统建设指南》系列的第五篇文章,该系列旨在告诉你如何来构建一个优秀的中后台管理系统

写在前面

上一篇文章中,我们在代码层面从零搭建起了一套代码规范体系

而在这篇文章中,我们会聚焦在工程层面,致力于从零搭建起一套 Git 提交规范化流程,通过工具如 HuskyLint-stagedCommitlintCommitizen,一步步实现 Git 提交规范化,首先,我们需要先了解一下这些工具的基础概念,请看下文

孟子曰:不以规矩,不能成方圆

基础概念

你可以在这里先了解下贯穿全文的工具概念,我们后面会使用到它们:

  • husky:一个 Git hook 工具,使用 Husky 可以挂载 Git 钩子,使现代的原生 Git 钩子变得简单,即可以帮助我们在 commit 前做一些自定义操作
  • Lint-staged: 对暂存的 git 文件运行 linters,也就是只对暂存的文件进行 ESLint、Prettier、Stylelint 等检查
  • commitlint:对 commit 信息进行检查,即 lint Commit Message
  • Commitizen:基于 Node.js 的  git commit  命令行工具,辅助生成标准化规范化的 Commit Message

git-hook

在开始前,我们先来了解一下什么是 git-hook,这是前置概念

hook 翻译过来是钩子的意思,有点类似于 Vue 的生命周期钩子一样,Git 能在特定的重要动作发生时触发自定义脚本

在我们执行 git init 命令后,会在当前目录下生成一个 .git 文件,目录中就存在一个 hooks 文件夹

Mac 用户如果看不到 .git 文件,使用 Command + Shift + . 来显示隐藏文件

└── .git
    ├── COMMIT_EDITMSG  # 保存最新的commit message
    ├── config  # 仓库的配置文件
    ├── description  # 仓库的描述信息,主要给gitweb使用
    ├── HEAD    # 指向当前分支
    ├── hooks   # 存放一些shell脚本,可以设置特定的git命令后触发相应的脚本
    ├── index   # 二进制暂存区(stage)
    ├── info    # 仓库的其他信息
    │   └── exclude # 本地的排除文件规则,功能和.gitignore类似
    ├── logs    # 保存所有更新操作的引用记录,主要用于git reflog等
    ├── objects # 所有文件的存储对象
    └── refs    # 具体的引用,主要存储分支和标签的引用

参考资料:https://blog.cti.app/archives/1344解析.git 文件夹,深入了解 git 内部原理

.git/hooks 目录下存放着一些 shell 脚本,以 .sample 为后缀,表示默认不启动,这些 shell 脚本主要分为两大类:客户端和服务端,我们这里只关注客户端

在客户端下,也细分一些钩子种类,比如 提交工作流钩子、电子邮件工作流钩子和其它钩子:

Pasted image 20240718224019

  • pre-commit  钩子在键入提交信息前运行
  • prepare-commit-msg  钩子在启动提交信息编辑器之前,默认信息被创建之后运行
  • commit-msg  存有当前提交信息的临时文件,如果该钩子脚本以非零值退出,Git 将放弃提交
  • post-commit  钩子在整个提交过程完成后运行

在文章开头,我们说这篇文章要做的事分为两类: lint 校验和 Commit Message 规范化,结合上面的钩子,所以我们的主要思路是:

  • pre-commit:在键入提交信息前进行 lint 校验,比如 ESLint、Stylelint、Prettier 等
  • commit-msg:对当前 Commit Message 进行检查并使用工具来规范化 Message 信息

要在 git 钩子里去做这些事情,我们就需要借助工具 Husky 来配置 Git 钩子,请看下文

Husky

这是什么?借用 Chat GPT 的话来说:Husky 是一个 Git hooks 工具,它可以帮助我们在 Git 操作执行前或执行后自动执行脚本

跟官网一样,首先安装它

pnpm add --save-dev husky

并且使用 Husky 推荐的 init 命令

pnpm exec husky init

这条命令会在根目录下新增一个 .husky 目录,目录下会创建  pre-commit  脚本

并在  package.json  中生成一个名为 prepare 的脚本

Pasted image 20240722220349

在终端运行这个命令,来初始化 husky

pnpm prepare

它会在 .husky 下生成一个文件夹 _

确保你的项目中存在 .git 文件夹,否则出现找不到 .git 问题

Pasted image 20240731171724

好了,上面我们说,pre-commit 可以在键入提交信息前进行 lint 校验,现在,我们可以在这个钩子里做些事情了,但我们先来想象一个场景:

假设我们修改了文件 A 的代码并准备提交时,团队规范要求在提交前通过 lint 校验进行检查。通常情况下,全量项目的 lint 校验可能会耗费大量时间,而且有时会出现文件 B 因为 lint 校验未通过而被标记为错误,尽管我们并没有修改这个文件,而是其他同事提交的

能不能有一个方案,只 lint 我当前修改的文件,而不是全量 lint ,这样就可以避免 lint 的耗时,提高效率且有针对性

Lint-staged 就是用来解决这样的问题的,帮助我们在暂存的文件上执行 lint,而不是对整个项目进行全量校验。这样可以显著减少 lint 校验的时间消耗,提高效率,并确保校验的针对性

Lint-staged

lint-staged 在 Git 暂存文件上运行 linter

安装

pnpm add --save-dev lint-staged

配置

lint-staged 安装完成后,我们需要来配置一下它,这个做法和 ESLint、Stylelint 等很像

可以先看看 lint-staged-Configuration,即可以通过多种方式来配置,一种普遍的方式是直接在 package.json 中配置,比如

  "lint-staged": {
    "*.{js,ts,jsx,tsx}": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.vue": [
      "prettier --write",
	  "eslint --fix",
      "stylelint --fix",
    ]
  },

还有种方式是创建一个配置文件

本文中使用 lint-staged.config.js 来配置,并采用 ESM 模式,请确保 package.json 中包含了  "type": "module",参阅 使用 JS 配置文件

在根目录下新建一个 lint-staged.config.js ,这个文件默认导出一个对象,写入以下基础模版

/** @type {import("lint-staged").Config} */
export default {
  //...
};

lint-staged 会自动找到这个配置文件

然后,就可以在里面写入你的个性化配置,比如

/** @type {import("lint-staged").Config} */
export default {
  "*.vue": ["prettier --write --cache", "eslint --fix", "stylelint --fix"],
  "*.{js,ts,jsx,tsx}": ["prettier --write --cache", "eslint --fix"],
  "*.{css,scss,less}": ["prettier --write --cache", "stylelint --fix"],
  "*.html": ["prettier --write --cache", "stylelint --fix"],
  "*.json": "prettier --write --cache",
};

你可以在官网找到配置说明

注意,在运行指定的 linter 和代码格式化工具时,建议先运行 Prettier ,因为它会对代码进行整体格式化,这样可以确保代码在进行 lint 校验之前是一致且格式良好的状态

接下来,我们需要确保 Git 钩子 pre-commit 能够正确调用 lint-staged

在根目录下的终端运行 shell 命令:

echo "pnpm lint-staged" > .husky/pre-commit

这条命令的作用是将 "pnpm lint-staged" 写入到 .husky/pre-commit 文件中,推荐你阅读 husky-v9.0.1

Pasted image 20241208150933

示例

接下来我们测试一下上述配置是否生效,在这之前,请确保你已经做好了如下准备:

在项目里添加一个 test.ts 文件,一个 test.vue 文件(位置随意)

随意写点内容,比如

//test.ts
const name = "fifteen";
//test.vue
<script setup lang="ts"></script>

<template>
  <div>测试!!</div>
</template>

<style lang="less"></style>

将这两个文件提交到暂存区里:在终端输入命令 git add . 或者在 VS Code 中可视化操作

Pasted image 20240723222132

在终端执行命令 git commit -m 'test'

Pasted image 20240723222404

出现类似这样的画面,即表示 lint-staged 起作用了,可以看到,因为校验未通过,Commit 提交并未通过而是直接退出了

Commit 规范

在写下面章节之前,我想先来讲讲 Commit 规范的意义

试想一个场景,在工作中你接手了一个别人的项目,如何能够快速入手呢?除了基本的浏览代码,如果这个项目每次提交都遵循一定规范,比如 “feat:userManage Module”,那么项目的提交历史将变得规范而清晰,这很有助你接下来的上手工作,能了解项目的发展和主要功能,并找到相关的变更记录!

再比如团队的协同开发,你需要理解同事的部分业务代码,是否可以通过清晰、规范的提交信息来帮助我们了解这块的变更历史,从而简化交流达到提高效率的作用呢?

所以,commit 规范的意义体现在几个方面:

  1. 提高代码可读性和项目质量、改善项目可维护性
  2. 简化协作和沟通
  3. 方便版本管理和代码回溯
  4. 有助于自动化生成变更日志

这里不得不提到两个重要的提交规范:

  1. Angular 规范,由 Angular 团队制定并使用,也被社区广泛接受,推荐你阅读以下内容:
  1. Conventional Commits 规范则是由 Angular 规范发展调整而来的一个更通用的规范,官网的介绍是:约定式提交,一种用于给提交信息增加人机可读含义的规范,提供一套规则来创建清晰的提交历史:

如果你在开发 Angular 项目,那么遵循 Angular 提交规范是最优选择

如果你想要更通用的项目提交规范,那么选择 Conventional Commits 规范就行了,下文内容我们约定使用此规范

要让我们的提交信息能够符合 Conventional Commits 规范,可通过使用工具如 commitlint 来实现,请看下文

Commitlint

Commitlint 是一个用于检查 Git 提交信息是否符合规范的工具,用于在提交前自动检查提交信息

安装

pnpm add --save-dev @commitlint/{cli,config-conventional}

这条命令会在你的 package.json 文件的 devDependencies 中生成两个依赖:
Pasted image 20241208151333

  • @commitlint/cli,命令行工具
  • @commitlint/config-conventional,此配置遵循 Conventional Commits 规范,与 @commitlint/cli 配合使用,参阅 @commitlint/config-conventional

配置

类似的,我们需要一个配置文件,这里我们使用 commitlint.config.js

它也可以是一个 ts 格式的配置文件,但为了统一,我们这里使用 js

有两种方式配置,第一,在项目根目录下终端输入以下命令:

echo "/** @type {import('@commitlint/types').UserConfig} */ \n export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

这个 shell 脚本会在根目录下生成一个 commitlint.config.js 文件,并且写入以下内容:

Pasted image 20241207225729

第二种方式是手动创建

在项目根目录下新建一个 commitlint.config.js 文件,然后写入内容:

/** @type {import('@commitlint/types').UserConfig} */ 
 export default { extends: ['@commitlint/config-conventional'] };

一个基本的提交约定遵循以下模式:

type(scope?): subject
body?
footer?

然后,我们就可以写入一些自定义的规则项
比如,可以是这样的:

/** @type {import('@commitlint/types').UserConfig} */
export default {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "feat", // 新功能
        "fix", // 修复bug
        "docs", // 文档更新
        "style", // 代码格式(不影响代码运行的变动)
        "refactor", // 重构(既不是新增功能,也不是修改bug的代码变动)
        "perf", // 性能优化
        "test", // 增加测试
        "chore", // 构建过程或辅助工具的变动
        "revert", // 回滚到上一个版本
        "build", // 编译相关的修改,例如发布版本、对项目构建或者依赖的改动,
        "types", // 类型
        "ci", // CI 配置文件和脚本的更改
      ],
    ],
    "header-max-length": [2, "always", 100], //头部最大长度100
    "body-max-line-length": [2, "always", 100], //body最大长度100
    "footer-max-line-length": [2, "always", 100], //footer最大长度100
    "type-empty": [2, "never"], //type 不能为空
    "subject-empty": [2, "never"], //subject 不能为空
    // "scope-empty": [2, "never"], //scope 不能为空
    "type-case": [2, "always", "lower-case"], //type 小写
    "scope-case": [2, "always", "lower-case"], //scope 小写
  },
};

这些规则可以在 这里找到

在你写完自定义的配置规则后,我们要在创建提交之前对其进行 lint 校验,需要使用到 Husky 的 commit-msg 钩子,

commit-msg 概念:存有当前提交信息的临时文件,如果该钩子脚本以非零值退出,Git 将放弃提交

运行 shell 命令:

echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg

这个 shell 脚本会在 .husky 文件夹生成一个 commit-msg 文件,并且写入以下内容:

Pasted image 20241208151906

到这一步,我们已经实现了基本的 commit 信息校验,下面我们来试试

示例

测试一下对于提交信息的检测,看是否符合我们在 Commitlint 定义的规则

在终端输入命令:

pnpm exec commitlint --from HEAD~1 --to HEAD --verbose

这个命令的作用是检测你上一次的提交信息(前提是你有上一次提交信息),并让 Commitlint 输出更详细的信息

Pasted image 20240725224220

也可以来测试一下提交当前commit时,lint 校验是否起作用

在暂存区加一些测试文件,然后,输入 commit 信息

Pasted image 20240725224527

在这里,我的 commit 信息是 "ttt: a test" ,它的报错信息是:我的 type 类型不对,只能是枚举内的类型

我们上面配置了 lint-staged,所以会先对暂存的文件进行 lint

解决方法也很简单,修改 commit 信息使其符合规则即可

但你是否想过另一种方式?即通过自动化程序来避免这种纯手敲 commit 信息格式的错误呢

换句严谨点的话说,使用工具来给我们提供交互式命令行界面,创建符合 Commitlint 规范的 Commit 信息

社区有这样的解决方案,通过 Commitizen 来辅助我们生成一套标准化规范化的 Commit 信息,请看下文

Commitizen

Commitizen 是一个用于生成标准化规范化的 Commit 信息的命令行工具

在 Commitlint 官网的介绍中,有推荐一个 @commitlint/prompt-cli 来让我们创建交互式命令行,参阅这里

另一种代替方案是使用 Commitizen

我们简单比较一下这两者的区别:

  • @commitlint/prompt-cli 是官方提供的包,是 Commitlint 生态一部分,与 Commitlint 无缝衔接
  • Commitizen 拥有着更大的社区、更广泛的使用、更成熟,灵活及定制化

本文中我们使用 Commitizen

安装

pnpm add --save-dev commitizen

安装完后,我们还需要安装一个适配器,这里介绍两个:

这里使用 @commitlint/cz-commitlint

pnpm add --save-dev @commitlint/cz-commitlint

然后,你的 package.json 文件下的 devDependencies 会生成这两个依赖:
Pasted image 20241209111014

我们接着在 package.json 中写入以下内容:

{
  "scripts": {
    "commit": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "@commitlint/cz-commitlint"
    }
  },
}

配置

在做完上述的安装及基础配置后,我们可以自定义自己的交互文本,在 commitlint.config.js 文件中配置 prompt 属性,比如:

/** @type {import('@commitlint/types').UserConfig} */
export default {
  extends: ["@commitlint/config-conventional"],
  ignores: [(commit) => commit === ""],
  rules: {
	  //...规则,上文已经配置
  },
  prompt: {
    questions: {
      type: {
        description: "选择你要提交的变更类型",
        enum: {
          feat: {
            description: "新功能",
            title: "新功能",
            emoji: "✨",
          },
          fix: {
            description: "修复bug",
            title: "Bug修复",
            emoji: "🐛",
          },
          docs: {
            description: "仅文档更改",
            title: "文档",
            emoji: "📚",
          },
          style: {
            description: "不影响代码含义的更改(空白、格式化、缺少分号等)",
            title: "样式",
            emoji: "💎",
          },
          refactor: {
            description: "既不修复bug也不添加新功能的代码更改",
            title: "代码重构",
            emoji: "📦",
          },
          perf: {
            description: "提高性能的代码更改",
            title: "性能优化",
            emoji: "🚀",
          },
          test: {
            description: "添加缺失的测试或修正现有的测试",
            title: "测试",
            emoji: "🚨",
          },
          //更多...
        },
      },
    },
  },
};

这些配置你可以在这里找到

示例

在配置好上面的内容后,我们就可以来测试一下效果了

修改一些文件内容,然后 git add . 放入暂存区

在项目终端输入命令

pnpm commit

Pasted image 20240727002230

出现这个交互式命令界面即为成功

本文中的所有示例都可以在 vue-clean-admin 中找到

参考资料

了解更多

系列专栏地址:GitHub | 掘金专栏 | 思否专栏

实战项目:vue-clean-admin

专栏往期回顾:

  1. 收下这份 Vue + TS + Vite 中后台系统搭建指南,从此不再害怕建项目
  2. 中后台开发必修课:Vue 项目中 Pinia 与 Router 完全攻略
  3. 用了这些 Vite 配置技巧,同事都以为我开挂了
  4. 受够了团队代码风格不统一?7千字教你从零搭建代码规范体系

交流讨论

文章如有错误或需要改进之处,欢迎指正

@QFifteen QFifteen added the 通俗易懂的中后台系统建设指南 该系列旨在告诉你如何来构建一个优秀的中后台管理系统 label Dec 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
通俗易懂的中后台系统建设指南 该系列旨在告诉你如何来构建一个优秀的中后台管理系统
Projects
None yet
Development

No branches or pull requests

1 participant