結論から書くと、pre-commit hookを pre-commit に切り替えたメモ

保存時更新

このメモに表示されているtoken数は、frontmatterに記録されていて、以前は watchdog を使用して保存時に更新していた

from watchdog.events import FileSystemEvent, FileSystemEventHandler
from watchdog.observers import Observer

class MarkdownHandler(FileSystemEventHandler):

    def on_modified(self, event: FileSystemEvent) -> None:
        modified_path = Path(str(event.src_path))
        if modified_path.suffix != ".md":
            return
        if modified_path.name.endswith(".tmp.md"):
            return

        update(modified_path)  # insert token to the target markdown

if __name__ == "__main__":
    target = Path("<target_path>")
    observer = Observer()
    observer.schedule(MarkdownHandler(), str(target), recursive=True)
    observer.start()

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

保存時更新だと、ローカルで動かしているHugoが、ファイル保存時と、token数更新時それぞれに反応する。つまり Ctrl+S を押す度にいちいち2回Hugoがリロードされる。大した負荷ではないが気になったのと、リアルタイムでtoken数見る必要もないので、pre commit時の更新に切り替えることにした。

pre-commit hook

.git/hooks にサンプルがある。ここの pre-commit.sample を直接編集しても有効になるが、それだとGitで管理できないので、 .githook/pre-commit というファイルにスクリプトを書く。細かいエラーハンドリングやログ出力は省略している。

staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep '^content/posts/.*\.md$' || true)

if [ -n "$staged_files" ]; then
  for file in $staged_files; do
    if [ -f "$file" ]; then
      # Run token count and capture exit code
      if python token_count.py "$file"; then
        # Check if the file was modified by token_count.py
        if ! git diff --quiet "$file"; then
          git add "$file"
        fi
      else
        exit 1
      fi
    else
      exit 1
    fi
  done
fi

git config で登録すると有効になる。

$ git config core.hooksPath .githook

pre-commit install

最近は pre-commit が流行っている雰囲気。pre-commit に入れたいlintなどのツールは、シングルバイナリだったりpythonやjsスクリプトと多様な中で、それらを統合的に扱えるようにするのがこのpre-commitツールのモチベーション。

今回の場合は、submodule下で token_count.pyuv でインストールされていたので、以下のような entry を書いて起動する。

repos:
  - repo: local
    hooks:
      - id: token-count
        name: Token count updater
        entry: uv run --project scripts/token-count python scripts/token-count/token_count.py
        language: system
        pass_filenames: true
        files: '^content/posts/.*\.md$'

pre-commit スクリプトでGitのステージにあるファイルを渡す、みたいな処理が不要となり、YAMLで完結する。あとは pre-commit コマンドでインストール。

$ uv add pre-commit
$ uv run pre-commit install --hook-type pre-commit

実態としては .git/hooks/pre-commit が生成される。

1つだけだとをこのpre-commitで管理するうまみはそこまで高くないが、他のlinterやformatterを複数導入するとき、それらをYAMLで管理できるのは便利。

実際にコミットした時のログ

$ git commit -m "add new post, 1123"
Token count updater......................................................Passed
[main a13abcb] add new post, 1123
 1 file changed, 128 insertions(+)
 create mode 100644 content/posts/2025/23_pre_commit.md