• 作成:

Conventional Commitsに移行して、commitlintをグローバルに適用しました

自己流コミットメッセージをやめることにしました

今までの自己流コミットメッセージをやめることにしました。

これまでの自己流コミットメッセージは以下の記事で解説しています。

Gitのコミットメッセージでめっちゃタイプミスをするのでそろそろタイプミスを検出するhooksを書きました - ncaq

自己流コミットメッセージの確認スクリプトの最終版はこれで、 initなどへの対処を少し行っているのですかね。 commit-message-in-style at b216fa1ccd860308c60b6d69494b5c0c6a93bb19 · ncaq/dotfiles 既にリポジトリからは削除されています。

何故自己流コミットメッセージをやめるのかは、こういうのは流石に互換性が重要なので、既に普及しているものを使った方が良いと思うからですね。

これがプログラミング言語とかだとシェアの少ない言語でも優れた特性があるとかで選ぶんですけど、別にコミットメッセージの書式にたいした機能も無いので、今回ばかりは長いものに巻かれたほうが良さそうでした。

aws-cdkとかのFLOSSにコミットする時だけコミット検証を切ってそのコミットメッセージ規約に従うのも面倒ですし。

Conventional Commitsに移行

Conventional Commits に移行することにしました。

別に機能的にはsemverとの互換性があり、ある程度見栄えが悪くなければ何でも良いのですが。特に代替も無いですし。互換性を考えるとシェアが重要なので、 aws-cdkなどで使うことが義務になっているこれを採用することにしました。

流石にfeatとfixだけでは不足しているので公式ガイドでも触れられている、 Angular の規約を使うことにします。

commitlintを使用

手動でこれを守れる気は全くしないので、当然linterを入れます。

公式にも触れられている、 conventional-changelog/commitlint: 📓 Lint commit messages です。

globalに使用したい

ただ一つ問題があって、大抵の導入ガイドはGitリポジトリ一つずつに依存してセットアップしているのですよね。

今行っているプロジェクト的に、別にコミットメッセージを強要しなくても良いものもあります。強要した方がChangeLog生成的に楽になるのかなと思うものももちろんありますが。

また基本的にルールは同じなので自分が一人で書いている分には一々リポジトリに設定するのは面倒くさいです。今使ってるリポジトリに設定するにしても設定済みとしてないやつが混在して困惑するのは避けたいです。

なのでまずは、自分がコミットする場合にリポジトリに設定されて無くてもglobalに使用することを考えました。

まずgitのconfigで、 hooksPath = ~/dotfiles/git-hooks のようにグローバルなhookを設定します。

そのディレクトリのトップに公式の方法で、

yarn add @commitlint/{cli,config-conventional}
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

のようにプログラムをインストールします。

hookファイルcommit-msgを以下のように設定します。

#!/bin/bash
set -eu
yarn --cwd "$(dirname "$0")" commitlint --edit "$(realpath "${1}")"

commit-msgのワーキングディレクトリはそのgitリポジトリなので、 yarnの--cwdオプションでcommitlintが使えるようにします。そうした場合、公式ガイドの--editへの引数の渡し方ではgit-hooks以下のパスを見てしまうため、 realpathを使って絶対パス化します。

こうしてグローバルにcommitlintが使えるようになりました。プロジェクトローカルで一々作業をしなくて済みます。

footerが長いのは許す

URLやそれを含むMarkdownのリンク文法分割などは改行が不可能なので、コミットメッセージがある程度長くても許すようにしました。

bodyにも同じことが必要かもしれません。必要になった時に無効化します。

subjectの大文字小文字などは許す

特にこの記事のタイトルをそのまま載せようとしたらだめになった。固有名詞はどうにもならないので許します。

commitlint.config.js

module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    // URLやMarkdownのリンクなど改行出来ない要素が頻繁に頻繁に出現するため無効にする。
    "footer-max-line-length": [0, "always"],
    // 関数などの識別子などを直接コミットメッセージのタイトルに書きたいので無効にする。
    "subject-case": [0, "always"],
  },
};

Magitでコミットメッセージを書く時に簡単に入力できるようにする

参考リンク。

まずスニペットを書きます。説明も注釈とかで入れたいなあと思いましたが、一日使ってみたら慣れてきて不要に感じました。

# -*- mode: snippet -*-
# name: conventional-commits-type
# key: type
# --
`(yas-choose-value '(
"build: "
"chore: "
"ci: "
"docs: "
"feat: "
"fix: "
"perf: "
"refactor: "
"revert: "
"style: "
"test: "
))`$0

そしてそのスニペットを入力するコマンドを用意します。 yas-expand-snippetの挙動が分からなくて困惑してました。これにスニペットのnameやkeyを入れるものだとばかり思っていました。それでエラーにはならずにそのまま引数の文字列が出てくるから困惑です。型エラーになれば気が付きが早まったと思うのですが。実際はyas-lookup-snippetの返り値を入れることで解決です。ドキュメントは読んでいたのですが、最初流し読みした時yas-lookup-snippetと同じアルゴリズムで探索すると誤読してしまい、キーを入れることに走ってしまいました。

(defun yas-expand-snippet-conventional-commits-type ()
  "@commitlint/config-conventionalが受け付けるtypeを選択して入力する。"
  (interactive)
  (yas-expand-snippet (yas-lookup-snippet "conventional-commits-type")))

最初はgit-commit-setup-hookにこれを設定して、自動的に出てくるようにしていましたが、 amendの時とかにも勝手に出てくるのは困るなと思ったので、 M-zで起動するようにしました。キーは他のところではlsp起動に割り当てて使って無くて余ってる、一発で入れられるキーを探してただけなので、特に意味は無いです。

本当はMagitのdiffを見ながら書けるようにhelmのウィンドウ位置を弄るべきなのかもしれませんが、まあだいたい入れる内容は分かるでしょと思ってサボっています。そのうち改良するかもしれません。