Skip to main content

[note] git conventional commit

git conventional commit

過去曾經整理過如何透過 semantic-release 這套工具整合 CI/CD 來達到自動化更新套件版號、產生 CHANGELOG 檔,並發佈到 npm 的流程。

當時雖然有提到 conventional commit 這個撰寫 commit message 的規範(convention),但還沒有實際透過一些好用的工具來建立 commit message 和針對 commit message 進行檢查。

在這篇文章中就來介紹幾套用來建立符合 conventional commit 的好用工具:

  • 透過 commitlint 進行 commit message 的檢查(lint)
  • 搭配 husky 在建立 commit message 前就自動執行 commitlint
  • 透過 commitizen 方便開發者建立符合 conventional commit 的 commit message
  • 使用 conventional-changelog 根據 commit message 來產生 CHANGELOG 檔
  • 使用 standard version 來同時更新版本號和產生 CHANGELOG 檔

如果是對於 CI/CD 中如何自動更新版號的部分,則可以參考過去寫的系列文章:發佈 npm 套件 - 從手動到自動

commitlint:檢查 commit message#

commitlint 這套工具是用來作為 git commit 的 linter,並且可以搭配不同的 convention。

這裡選擇 config-conventional,也就是需要依據 conventional commit 的規範來寫 commit message:

  • @commitlint/cli 是用來執行 commitlint 的工具
  • @commitlint/config-conventional 是根據 conventional commit 所建立的規範

初次安裝#

# 安裝 commitlint-cli 和 config-conventionalnpm install --save-dev @commitlint/{config-conventional,cli}
# 會在專案中建立 commitlint.config.js 並放入設定echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

使用#

# 使用 commitlint$ echo "add commitlint" | npx commitlint> ✖   subject may not be empty [subject-empty]>type may not be empty [type-empty]

如果這個 commit message 不符合規範的話,會跳出錯誤:

commitlint

搭配 husky#

但和 eslint 類似,如果不能在建立 git commit message 時就自動檢查規則的話,這個工具就會變得有點冗,這時候可以搭配 husky 這套工具。

# 第一次安裝 husky 才需要執行$ npx husky-init && npm install
# 建立 commitlint 用的 git hooknpx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'

husky 這套工具可以讓開發者在不同的 git hook 執行不同的動作,例如在建立 commit 前(pre-commit)執行 ESLint 的檢查,如果檢查沒過就不能建立該次 commit。

這裡則是利用 husky 在 commit-msg 這個 git hook 去檢查 commit message 有沒有符合 conventional commit 的規範。這時打開專案中的 .husky/commit-msg,應該會長這樣:

#!/bin/sh. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1
note

預設執行 npx husky-init && npm install 的指令後,husky 會自動在 .husky 資料夾中建立 pre-commit 的檔案,它會告訴 husky 在建立 git commit 前去執行 npm test 的指令。但如果是 create-react-app 的專案,因為預設 npm test 會執行的是 watch mode,因此需要改成 npm test -- --watchAll=false

這時候在建立 commit message 是就會自動使用 commitlint 進行檢查,如果不符合規範的話,就無法成功建立 commit:

commitlint with husky

commitizen:建立 commit message#

除了可以用 commitlint 搭配 husky 來檢查 commit message 之外,再來很重要的就是要能夠簡單方便的建立符合 conventional commit 的 message。因此就有幫助開發者建立 conventional commit message 的好用工具。

其中 commitizen 是同事 Ken 推薦非常多人使用的工具,它用起來的體驗蠻不錯的。另一套則是 commitlint 本身提供的 @commitlint/prompt-cli

commitizen(較推)#

commitizen 是非常多人使用的的工具,相較於 @commitlint/prompt-cli 用起來感覺更友善一些:

# 安裝npx commitizen init cz-conventional-changelog --save-dev --save-exact
# 之後要建立 commit 的話,只需要執行npx cz

commitizen

在 global 使用 commitizen#

如果你不想要把每個專案都透過 npx commitizen init 變成 commitizen-friendly 的專案,想要可以直接在全域下使用 commitizen,可參考 Conventional commit messages as a global utility 這麼做:

npm install -g commitizen  # install commitizen globallynpm install -g cz-conventional-changelog # Install commitizen adapter globally
# create `.czrc` in home directoryecho '{ "path": "cz-conventional-changelog" }' > ~/.czrc
# 建立 git commit 時,只需要使用$ git cz

@commitlint/prompt-cli#

除了可以使用 commitizen 外,也可以使用 @commitlint/prompt-cli 來輔助我們建立 git commit message,但基本上挑一套使用就可以了。

# 初次安裝npm install --save-dev @commitlint/prompt-cli

安裝完後執行:

# 建立 commit messagenpx commit

就會跳出對應的 CLI 工具來協助建立 git commit:

@commitlint/prompt-cli

conventional-changelog:建立 CHANGELOG#

在根據 conventional commit 來寫 commit message 後,我們還可以自動產生對應的 CHANGELOG 檔。這裡則會使用 conventional-changelog-cli

只需要執行:

  • -p angular:如果有使用 conventional commit 來建立 commit message 的話,就可以加上此選項。它會用符合 conventional message 的內容來產生 CHANGELOG;如果沒加此參數的話,所有 git message 都會進到 changelog 中。
# 第一次安裝$ npm install --save-dev conventional-changelog-cli
# 檢視可以使用的 options 和說明$ npx conventional-changelog --help
# 第一次產生 CHANGELOGnpx conventional-changelog -p angular -i CHANGELOG.md -s -r 0
# 將新的更新 message 添加到 CHANGELOGnpx conventional-changelog -p angular -i CHANGELOG.md -s

也可以把產生 CHANGELOG 的指令放到 package.jsonscripts 中:

diff --git a/package.json b/package.jsonindex 18f0899..37a1d41 100644--- a/package.json+++ b/package.json@@ -21,6 +21,7 @@  "scripts": {     "build": "react-scripts build",     "test": "react-scripts test",     "eject": "react-scripts eject",+    "changelog": "conventional-changelog -i CHANGELOG.md -s,     "lint:staged": "lint-staged",     "prepare": "husky install"   },@@ -51,6 +52,7 @@  "devDependencies": {     "@commitlint/cli": "^12.1.4",     "@commitlint/config-conventional": "^12.1.4",+    "conventional-changelog-cli": "^2.1.1",     "cz-conventional-changelog": "^3.3.0",     "husky": "^7.0.0",     "lint-staged": "^11.0.0"

之後就只需要執行 npm run changelog 就會產生最新的 CHANGELOG 檔。

standard version:更新套件版本號#

最後這套 standard version 是在針對套件進行版號更新,並同時產生該次更新的 CHANGELOG 檔,也就是說不需要再額外使用上述 conventional-changelog 的工具。

tip

如果你產生 CHANGELOG 的時間都是在更新版本號時,且是使用 conventional commit 在建立 commit message 的話,則可以用 standard version 就好,不用再使用 conventional-changelog。

它會:

  1. 根據 conventional commit 的內容,依據 semver 的原則來更新版號
  2. 產生對應的 CHANGELOG
# 檢視所有可用的指令npx standard-version --help
# 第一次 releasenpx standard-version --first-release
# 更新套件版號和 CHANGELOGnpx standard-version
# 透過 dry-run 先看看會有什麼改變npx standard-version --dry-run
# 更新到指定的版本npx standard-version --release-as minor # 指定更新 minor 的版號npx standard-version --release-as 1.1.0 # 指定更新後的版號
caution

需要特別留意的是,當版號還在 v0.y.z,major 還沒進到 v1 是,會被視為是 pre-production 的產品,因此 feature 和 fix 都只會更新 patch version;breaking change 則只會更新 minor version。可以參考這個 issue 的討論:Version bumping isn't working properly based on Commit types

另外有需要的話,也可以把這個工具加到 package.json 中的 scripts 中:

diff --git a/package.json b/package.jsonindex d18c992..9832508 100644--- a/package.json+++ b/package.json@@ -22,6 +22,7 @@     "test": "react-scripts test",     "eject": "react-scripts eject",     "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",+    "release": "standard-version",     "lint:staged": "lint-staged",     "prepare": "husky install"   },

執行的話變成:

# 更新版本npm run release
# 執行 dry-run 看實際執行的話會有什麼改變npm run release -- --dry-run

說明影片#