提交信息 (commit message)

diff

在提交前最好先确认一下当前提交了什么文件,看看 diff,确认没有改错后再提交。

编写方式

提交信息可以通过以下命令完成:

1
  git commit -m 'your message'

但是它的缺点是能写的内容不多, 有时你可能有一些补充信息,或者需要附上一些 issue 链接,就不方便了。

所以建议是配置好 Git 使用的编辑器,然后直接敲 git commit, 在编辑器中进行编写。

配置 Git 编辑器:

1
2
3
4
5
  # vim 可以换成你习惯的编辑器
  git config --global core.editor "vim"

  # 或者设置 GIT_EDITOR 变量,在你的 .bashrc / .zshrc 文件中添加即可
  export GIT_EDITOR=vim

格式

提交信息一般遵循约定式提交的规范去编写,基本结构为:

1
2
3
4
5
<类型>[可选的作用域]: <描述>

[可选的正文]

[可选的脚注]

其中类型的定义可以参考 Angular:

  • build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)

  • ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)

  • docs: Documentation only changes

  • feat: A new feature

  • fix: A bug fix

  • perf: A code change that improves performance

  • refactor: A code change that neither fixes a bug nor adds a feature

  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)

  • test: Adding missing tests or correcting existing tests

提交信息的编写可以参考 Introduction: Why good commit messages matter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  Summarize changes in around 50 characters or less

  More detailed explanatory text, if necessary. Wrap it to about 72
  characters or so. In some contexts, the first line is treated as the
  subject of the commit and the rest of the text as the body. The
  blank line separating the summary from the body is critical (unless
  you omit the body entirely); various tools like `log`, `shortlog`
  and `rebase` can get confused if you run the two together.

  Explain the problem that this commit is solving. Focus on why you
  are making this change as opposed to how (the code explains that).
  Are there side effects or other unintuitive consequences of this
  change? Here's the place to explain them.

  Further paragraphs come after blank lines.

   - Bullet points are okay, too

   - Typically a hyphen or asterisk is used for the bullet, preceded
     by a single space, with blank lines in between, but conventions
     vary here

  If you use an issue tracker, put references to them at the bottom,
  like this:

  Resolves: #123
  See also: #456, #789

工具校验

在团队协作中,为了确保大家的提交都符合规范,最好是通过工具去约束,而不是人为检查。

格式的约束可以通过 commitlint 等工具实现,可以添加一个 pre-commit 的钩子,在提交前进行格式的校验。

提交信息模板(commit.template)

团队中可能有约定好的提交信息格式,也可以添加一份提交模板在项目,每次提交时都读取模板进行编写,这样也可以统一格式。

可以在项目中根目录创建一份 .gittemplate.txt , 然后写入模板内容:

1
2
3
4
5
6
Subject line (try to keep under 50 characters)

Multi-line description of commit,
feel free to be detailed.

[Ticket: X]

然后给项目的 git 仓库配置使用这个模板(具体配置会生成在 项目根目录/.git/config 下):

1
  git config commit.template .gittemplate.txt

然后下次提交时,提交信息就会变成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  Subject line (try to keep under 50 characters)

  Multi-line description of commit,
  feel free to be detailed.

  [Ticket: X]
  # Please enter the commit message for your changes. Lines starting
  # with '#' will be ignored, and an empty message aborts the commit.
  # On branch master
  # Changes to be committed:
  #   (use "git reset HEAD <file>..." to unstage)
  #
  # modified:   lib/test.rb
  #
  ~
  ~
  ".git/COMMIT_EDITMSG" 14L, 297C

githooks / husky

前面提到,可以用 commitlintpre-commit 时进行校验, pre-commit 指的就是一个 Git 钩子,可以在提交前触发一些动作。

你可以在提交前后,push 前后,合并前后去触发一些动作。

常见的配置例如:

  • 在提交的时候检查代码格式,格式化代码 (执行 eslint, prettier)

  • 在确认提交信息后,校验提交信息的格式

  • 在 push 到远端时,校验代码是否有错误,跑测试用例,比对分支看是否缺少上游提交等

githooks 最早出现的版本时 2.19.0 ,如果你的 Git 版本过低,就需要升级一下。

配置方法

githooks

  • 进入 githooks 所在的目录

    1
    
      cd /project-repo/.git/hooks

    你会看到有这些文件:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
        -rwxr-xr-x 1 spike spike  478 Oct 27 19:10 applypatch-msg.sample
        -rwxr-xr-x 1 spike spike  896 Oct 27 19:10 commit-msg.sample
        -rwxr-xr-x 1 spike spike 3079 Oct 27 19:10 fsmonitor-watchman.sample
        -rwxr-xr-x 1 spike spike  189 Oct 27 19:10 post-update.sample
        -rwxr-xr-x 1 spike spike  424 Oct 27 19:10 pre-applypatch.sample
        -rwxr-xr-x 1 spike spike 1638 Oct 27 19:10 pre-commit.sample
        -rwxr-xr-x 1 spike spike  416 Oct 27 19:10 pre-merge-commit.sample
        -rwxr-xr-x 1 spike spike 1348 Oct 27 19:10 pre-push.sample
        -rwxr-xr-x 1 spike spike 4898 Oct 27 19:10 pre-rebase.sample
        -rwxr-xr-x 1 spike spike  544 Oct 27 19:10 pre-receive.sample
        -rwxr-xr-x 1 spike spike 1492 Oct 27 19:10 prepare-commit-msg.sample
        -rwxr-xr-x 1 spike spike 3610 Oct 27 19:10 update.sample
  • 根据需要的钩子,将 .simple 后缀移除,例如 pre-commit.sample 改为 pre-commit

  • 打开文件,设置为你想写的语言

如果你想用 shell:

1
  #!/usr/bin/env sh

或者用 python:

1
  #!/usr/bin python
  • 设置好语言后,就编写脚本,例如 pre-push:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
      #!/bin/sh
      COMMIT_ID=$(git log -n 1 --pretty=format:"%H" main)
    
      if git merge-base --is-ancestor $COMMIT_ID HEAD
      then
          exit 0
      else
          echo "主分支有你没有合并的提交,在提测前请合并"
          exit 0
      fi
  • 保存好脚本后, 当 push 的时候,就会执行 pre-push 里面的脚本,去比对分支,如果不同步则会出现提示文字

但是 githooks 是 存储在本地,不进入版本管理 的。

一个解决办法是在项目中创建一个目录存放 hooks, 同步到远端后,大家拉取下来拷贝 .git/hooks 目录里。

husky

husky 是一个方便写 githooks 的工具,目前也蛮流行的。

它会在项目的根目录下创 .husky 目录,存放 husky 生成的所有 hooks,这样可以避免 hooks 在本地仓库没有同步的问题。

它还会修改 githooks 对应的路径,改写为 .husky 目录, 如果你打开 /project-root/.git/config , 你会看到:

1
2
  [core]
          hooksPath = .husky

husky 的使用就不赘述了,如果项目可以 husky,建议还是用 husky 来维护 githooks。

一些例子

1
2
3
4
5
6
7
8
    /home/spike/git/commit-demo/.husky:
    total used in directory 24 available 232.5 GiB
    drwxrwxr-x 3 spike spike 4096 Nov 17 17:48 .
    drwxrwxr-x 6 spike spike 4096 Nov 17 17:48 ..
    drwxrwxr-x 2 spike spike 4096 Oct 25 09:14 _
    -rwxr-xr-x 1 spike spike   88 Oct 25 09:15 commit-msg
    -rwxr-xr-x 1 spike spike   68 Oct 25 11:06 pre-commit
    -rwxr-xr-x 1 spike spike  218 Nov 17 17:48 pre-push

校验提交信息 (commit-msg)

安装 commitlint 以来,然后创建 commit-msg 钩子,在提交后,校验提交信息是否符合规范:

1
2
3
4
  #!/usr/bin/env sh
  . "$(dirname -- "$0")/_/husky.sh"

  npx --no -- commitlint --edit ${1}

提交前校验&格式化代码(pre-commit)

1
2
3
  #!/usr/bin/env sh
  . "$(dirname -- "$0")/_/husky.sh"
  npx lint-staged

这里用到了 okonet/lint-staged, 它的作用是对 staged 中的内容(git add 进去的内容)进行一些处理。

.lintstagedrc.json:

1
2
3
  {
    "src/*.{js,vue}": ["prettier --write", "eslint"]
  }

例如这里就是对 src 下的 js 和 vue 类型的文件,用 pretteir 格式化后,再用 eslint 去校验。

推送时判断是否合包含主分支提交(pre-push)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  #!/bin/sh
  # 获取 main 分支最新的提交
  git fetch
  COMMIT_ID=$(git log -n 1 --pretty=format:"%H" origin/main)

  # 判断 main 的最新提交 是不是 当前的最新提交 (HEAD) 的上游
  if git merge-base --is-ancestor $COMMIT_ID HEAD
  then
      exit 0
  else
      # 如果不是则提示 exit code 不为 0,会阻断 push,无法推送到远程
      echo "主分支有你没有合并的提交,在提测前请合并"
      exit 1
  fi

当存在多条分支时,有时容易忘记合并主分支代码,导致测试时丢失主分支的提交。

你就可以写一个 pre-push 钩子,再 push 的时候去校验一下。

changelog

涉及到版本控制,自然也有版本号和发布日志,一般而言,版本号应该遵循语义化版本 2.0.0的规范。

而发布日志,一般而言也可以用一个 CHANGELOG.md 文件去编写和维护。

changesets 可以方便地让你按照语义化的规范去编写 changelog。

当做了一些修改,觉得有必要再发布日志里说明,就可以添加一条 changeset:

1
  npx changeset

changeset 会要求你选择当前这条记录对应的语义化版本,从而约束你去遵守语义化。

在发布前可以不断地添加 changeset,等到要发布时,执行:

1
  npx changeset version

changeset 就会帮你把所有的 changeset 合并到 CHANGELOG.md 中,同时根据你之前的选择计算对应的版本号。