世界を動かす技術を、日本語で。

Prek: Rustで開発された、より良く、より速い、ドロップインのプレコミット置き換えツール

概要

prek は、 pre-commit の高速・依存レスな代替ツール。 Rust製で、 単一バイナリ ・多言語対応・ディスク効率化を実現。 CPythonFastAPI などの有名プロジェクトでも導入実績。 pre-commit 設定やフックと高い互換性を維持。 多彩なインストール方法と、 monorepo や各種言語ツールチェーンへの対応が特徴。

prekとは何か

  • pre-commit の再設計版としてRustで開発されたフック実行フレームワーク
  • PythonやNode.jsなど、多言語で書かれたフックの実行・依存管理を自動化
  • 高速動作依存レスディスク効率化 を重視した設計
  • pre-commit の設定・フックと互換性を持つドロップイン代替
  • monorepo(ワークスペースモード) 対応
  • uv との連携によるPython仮想環境・依存管理
  • Python, Node.js, Bun, Go, Rust, Rubyなどのツールチェーンをフック間で共有
  • 一部フックはRustネイティブ実装で更なる高速化

主な特徴

  • 単一バイナリ で動作、Pythonや他のランタイム不要
  • pre-commit よりも数倍高速、ディスク消費も約半分
  • オフライン・即時利用 可能なbuiltinフックサポート
  • フック・リポジトリの並列クローン・インストール・実行 で全体の効率向上
  • Python仮想環境 作成・依存インストールの高速化(uv利用)
  • 直感的なコマンド拡張 (--directory, --last-commit, 複数フック同時実行など)
  • フック一覧表示自動アップデート 機能
  • シェル補完 によるコマンド入力支援
  • サプライチェーン攻撃対策 (auto-update時の--cooldown-days等)

インストール方法一覧

  • スタンドアロンインストーラー (Linux/macOS/Windows対応)

    • curl/powershellによるワンライナー
  • PyPI (pip, uv, pipxでインストール可能)

  • Homebrew

  • mise

  • cargo-binstall (Rustユーザー向け)

  • Cargo (ソースからビルド、Rust 1.89+必須)

  • npmjs (Node.jsパッケージとして、npm/pnpm/bun対応)

  • Nix/Nixpkgs

  • conda-forge

  • Scoop (Windows用)

  • MacPorts

  • GitHub Releases (バイナリ直接ダウンロード)

  • GitHub Actions (CI/CD統合用)

    • 例: j178/prek-action, taiki-e/install-action

導入・使い方

  • 既存プロジェクトで pre-commit からの移行はクイックスタートガイド参照
  • 新規導入時は設定ファイル作成、フック実行、git hookインストールの流れ
  • prek run コマンドで柔軟なフック実行が可能

他ツールとの違い・優位点

  • pre-commit よりも高速・効率的な環境構築とフック実行
  • フックごとの環境を共有 することでディスク消費・インストール時間を削減
  • 並列処理 による全体パフォーマンス向上
  • uv による高速なPython仮想環境管理
  • Rustネイティブフック で更なる高速化
  • repo: builtin によるオフラインフック対応
  • monorepo でのサブプロジェクト個別設定サポート
  • 使いやすさ向上 のためのコマンド・補完機能・一覧表示

導入実績・利用プロジェクト

  • CPython
  • Apache Airflow
  • FastAPI
  • Typer
  • Ruff
  • OpenClaw
  • Home Assistant
  • OpenLineage
  • Authlib
  • Django Project
  • Requests-Cache
  • python-attrs
  • Apache Iceberg
  • Lucene
  • msgspec
  • humanize
  • MoonshotAI/kimi-cli
  • simple-icons
  • ast-grep
  • commitizen
  • cocoindex
  • cachix/devenv など

謝辞

  • pre-commit のオリジナル開発者・コントリビューターへの感謝
  • Astralチーム (特にuvプロジェクト)からの多大な技術的学び
  • Rustでの効率的・イディオマティックな実装手法の参考

prek は、次世代のフック実行基盤として注目されており、今後もさらなる対応言語や機能拡張が期待されるツール。 pre-commit ユーザーや高速・効率化を求める開発現場におすすめ。

Hackerたちの意見

gitフックの価値がよくわからないんだよね。オプトインで、簡単にオプトアウトできる方法でシェルスクリプトを呼び出すって感じかな。強制的に実行させることはできないし、CI/CDとの統合もイマイチだし。直接シェルスクリプトを呼び出せばいいんじゃない?CI/CDプラットフォームでどう使うの?

価値があるのは、プッシュする前にローカルで失敗することがわかることだね。エージェントにも人間にも役立つ。

俺は逆のやり方をすることが多いかな。CIのステップで定義されたものをpre-commitに追加する感じ。いくつかのツールは既存の設定があるし、ローカルモードも使える。確かに人に強制することはできないけど、CIが失敗するから時間を節約できるよ。

明らかにバイパスできるけど、precommitフックを使ってローカルでスクリプトを実行して、特定のチェックが通るか確認することで、パイプラインでの失敗を防げるから、時間とお金を節約できるよ。組織の観点からすると、開発者体験の一部としてそれを義務付けることもできるね。(うちのチームは使ってないけど、潜在的な価値はわかる)

pre-receiveフックが好きなんだ。

コミット中以外にも、pre-commit/prekはrunで全てのフックを実行できるよ。だからCI/CDでは、すべての個別のlint/formatツールの呼び出しをpre-commit/prekへの一回の呼び出しに置き換えられる。例えば、https://github.com/python/cpython/blob/main/.github/workflow....

CIではよく使われてるよね。pre-commitやprek用の専用GitHubアクションもあるけど、たいていの人は普通にprek run --all-filesとかpre-commit run --all-filesを使ってリントCIジョブを実行してる。prekのドキュメントには、CPythonやFastAPIみたいな大きなプロジェクトが使ってるリストが載ってるよ。もっと詳しく見たいなら、各リンクはCIに統合した方法のPRだよ:https://prek.j178.dev/#who-is-using-prek

CIとよく統合されてるよ。CIでもローカルと同じフックを実行するから、DRYで、早めにフィードバックを得るためにローカルでもフックを使うようになる。CIなしのフックはあんまり役に立たないよ、常に壊れちゃうからね。

gitフックには価値があると思うけど、pre-commitは間違ったフックだね。これはコミットじゃなくて、プッシュを試みたときに実行されるフックにすべきだよ。

これは私の問題かもしれないけど、私はいつもgitの履歴を徹底的に操作してるから、gitフックが嫌いなんだ。コミットはミリ秒で終わるべきで、1分もかかるなんてありえない。

俺だけかな?pre-commitでパフォーマンスの問題に遭遇したことがないんだけど。確かにLinuxカーネルみたいな大きなプロジェクトには関わってないけど、不満はないよ。

pre-commitをあまり使ったことはないけど、たまに使うこともある。なんでこのプロジェクトが存在する必要があるのか全然わからないんだけど?pre-commitがパフォーマンスの問題を引き起こすことってあるの?フックされたプロセスが長くなることは理解できるけど、pre-commit自体は?なんで時間がかかるの?

問題は一度もなかったよ。各コミットにかかる時間は微々たるもので、いくつかのフックを使ってる。テストを実行する方が何倍も時間がかかる。

使ってるフックやその数によるかな。いくつかの言語には結構遅いフックがあって、大きなモノレポで使うと時間がかかることがある(私の職場のメインリポジトリをフルで走らせるのに数分かかる)。Pythonベースのフックを常に更新してると、仮想環境のインストールや作成も遅くなるから、prekがそれを早くしてくれる。

私にとっての問題はパフォーマンスじゃなくて、フックの更新ロジックが悪いことなんだ。彼らはフックをタグの独占的な使用で孤立したリポジトリで管理することを期待してる。私のプログラムのリポジトリにはghアクションとフックがあって、それがユーザーにとっては痛い目の元になってるけど、リポジトリ間の更新を扱うよりはpre-commitのサポートをやめた方がいいと思ってる。prekがこれを解決してくれたんだ。

http://hk.jdx.dev/を使ってるよ。これはhttps://pkl-lang.org/とRustに基づいてて、http://mise.jdx.dev/と統合されてる。prekの方がずっと良いの?

prekはpre-commitと互換性があるから、pre-commitで使えるフックは全部prekでも使えるよ。リポジトリの設定ファイルも含めてね。既存のpre-commitエコシステムに興味があるなら、かなり広範囲だから、prekは本当に良い代替手段だと思う。

miseが大好き!hkのことは知らなかったけど、これもチェックしてみるよ。ただ、今のところ$WORK(私自身も)にはlefthook以上のものは必要ないかなって思ってる。今のところは満足してるしね。ユニークな価値提案を示す比較やプロジェクトがあるといいな。

pre-commitプラグインベースで構築するのは大きな間違いだと思う。pre-commitはおそらく最も人気のあるpre-commitフックのツールだけど、プラットフォームはイマイチ。主な批判点は、ツールのインストールとリントを混ぜてしまってるところ。フックの外でリントを使いたいのにね。インターフェースは並列処理を考慮してないし、なんか無理やりくっつけた感じで、実際にはうまく機能しないと思う。さらに、ランダムなオープンソースリポジトリをたくさん使ってるから、ピン留めしてもサプライチェーンの悪夢だよ。私の意見では、pre-commitは有害だと思う。prekは大体改善されてるけど、すでにひどいプラットフォームの上に改善してるから、使うべきじゃないと思う。競合ツールを作ってるのは知ってるけど、lefthookやhuskyに対しては同じ批判はしないよ。あれは大丈夫だし、シンプルさの面ではhkよりもいいと思う。

彼らは何らかのプラグインや拡張フレームワークを実装する必要があると思う。拡張は一級の市民ではないけど、実際にはそうあるべきだよ。リポジトリには、.gitignoreや.gitattributesみたいに、リポジトリの所有者が管理する.gitextensionsが必要だと思う。すべてのユーザーがオプトインできるようにしても、少なくともすべてのgitクライアントがそれを知って、ダウンロードして、ユーザーの裁量でインストールできるようになるべきだ。今の時代には基本的なことだと思うけど、まだ大きな穴があるよ。LFSのインストールを手動で呼び出す必要があるなんて、ほんとにありえない。

私もRustで書かれた代替案に取り組んでるけど、私のバージョンではフックがWASIプログラムになってる。Gitリポジトリにバックアップされた仮想ファイルシステム上で実行されるんだ。つまり、a) セキュリティの問題はない(ネットワークアクセスもリポジトリ外のファイルアクセスもない)、b) 並列で実行できる、c) 修正を適用するかどうかをプラグインの明示的なサポートなしで選べる、そして最も重要なのはd) 確実に動作すること。これはpre-commitよりも信頼性が高いと思うけど、それでもPythonのホイールをビルドするフックがあって、これがしょっちゅう失敗するのがイライラする。https://github.com/timmmm/nit VFSの部分はまだ完成してないけど(本当に複雑なんだ)。誰か手伝ってくれると嬉しいな!

フックがコードを変更した瞬間、サンドボックスが壊れちゃうよね。WASIはこの問題を解決するいい方法だと思うけど、セキュリティが理由ってわけじゃないかな。

私はprekの大ファンで、いくつかのプロジェクトをpre-commitから移行したよ。私にとっての主な利点は、prekがモノレポやワークスペースをサポートしていて、既存のpre-commitフックとも互換性があること。だから、ルートの下の各ワークスペースに追加の.pre-commit-config.yamlファイルを持てるし、コミットするときにprekがそれらを見つけて実行してくれる。結果もきれいにまとめられるし、ただ動くんだ。Rustで再実装されたデフォルトのフックはちょっとしたボーナスだし(サードパーティのフックはそれほど速くならないだろうけど)、uvをパッケージマネージャーとして使うことでPythonのフックの更新も早くなるよ。

prekを使うのがめっちゃ楽しい!最近出した本『Effective Testing』の中で、これに特化した章を丸々一つ使ったよ。Rustを使った速いコアと便利なラッパーのトレンドは、コードを書いてる間は最高だね。

ちなみに、pre-commitフックはこの手のことには向いてないと思う。JJには、変更ごとにバックグラウンドで「チェック」を実行するちゃんとしたデーモンを作ってほしいな。コミット時にpre-commitチェックを実行するんじゃなくて、バックグラウンドで自動的に行われて、変更を共有する頃には、すべてのことが確認済みになってるって感じ。無駄な時間を使わずに特別なことをする必要もないよ。SelfCI(ミニマリスティックなローカルファーストのUnix哲学に基づくCI)に似たようなものを実装してて、これがpre-commitフックを完全に置き換えたんだ。ユーザーからも、ちゃんとしたコミットフックのように感じるって言われたよ。

それめっちゃクールだね!JJのフックに対してもっと考えられたアプローチを探してたから、これを掘り下げてみるよ。他にそのリポジトリにあるドキュメント以外で、もう少し高レベルなアーキテクチャや概要のドキュメントってある?今のドキュメントは「これが何をするかはもう知ってるべき」って感じがするし。Radicleはどう思ってる?

それいいアイデアだね!自己ホスト型のCIと組み合わせることを考えてたところだった。基本的には、早めに頻繁にコミットしたいから、コミットを書いてから、サンドボックス環境でリント(とテスト)を実行したいんだ。通ればラッキー、失敗したら、HERADが失敗したコミットの先に進んでたら、「FIXME」ブランチを作る感じ。メインや他のブランチに戻って、テストが通り始めたら、失敗を再訪する必要はないかも。フルCIでリモートにプッシュする前に、ローカルのテスト失敗について知りたいんだ。自動ブランチ作成やワークフローのことはオプションで、コアのアイデアは素晴らしいよ。

そうだね、ファイルの変更をトリガーするにはウォッチャーの方が合ってると思う。個人的には、gitのコミットコマンドが遅かったり失敗したりするのは耐えられない。 [0]: 例えば https://github.com/watchexec/watchexec

このアプローチ好きだな。関連していじってるのは「保護されたブックマーク」なんだ。config.tomlでどのブックマーク(mainとか)が保護されてるか宣言して、ブックマークポインタを変更する通常の jj bookmark コマンドはフラグを渡さない限り失敗するんだ。だから、ローカルの「CI」スクリプトでは、テストやリンティングが通った場合に限り jj bookmark set main -r@ --allow-protected ができる。ワークスペースやローカルCIを実行する何か(ウォッチャーや他の自動化プロセス)と相性がいい。まだデザインの議論のために上流に提出してないけど、自分のブランチをプッシュしたよ。[1] 追加のベルトとサスペンダーのために、ターゲットリビジョンが一致するrevsetを宣言することもできる(例: '~conflicts()') [1] https://github.com/paulsmith/jj/tree/protected-bookmarks

可視化されるのは便利だね。これはフックやデーモンよりもIDEに合ってるかも。

ソースコードのために、Mayaのような多層反応DAGが欲しい。

すごく興味深いね!ローカルでCIを動かすのは全然アリだと思う。ただ、READMEをざっと見た感じで、gitとの統合に関するベストなパターンが分からなかったんだ。ユーザーには手動でselfciを実行させるつもりなの?それともgitとかに連携してるのかな?マージフックはいつ使うの?selfciにマージを頼むの?

フックに「pre-commit」ってラベルが付いてるからって、必ずしもコミット前に実行しなきゃいけないわけじゃないよね :)。私もjjで変更ごとにチェックしたいけど、(まだgitを使ってる人たちと一緒に作業する必要があるから)コミットサイクルの同じタイミングで実行しなくても同じチェックが使えるようにしたいんだ。だから、コミットを検証したいときに実行するためのエイリアス jj pre-commit を作ったし、@に対して定義されたコミットのセットで実行する jj pre-commit-branch もあるよ。内部では pre-commit を使ってるから、gitユーザーの pre-commit ツールとの互換性は保ってる。まだバックグラウンドでチェックを実行したり、jjのデータストアにチェックのステータスを保存することはできないけど、合格したチェックのツリーは保存してるから、再実行はすごく早いよ。

git ls-files | entr pre-commit

みんな、以下の組み合わせでは置き換えられないようなプレコミットフックの使い方の例を教えてくれない? * CI(プレコミットはエラーを左にシフトさせるのは分かる) * エディタやIDEでのタイプチェックやリンターの自動フォーマットのためのライブエラーコールアウト。テストは実行してる?どのテストを実行するかどうやって知るの?CIが実行するすべてのテストを走らせるのは遅くなるかもしれないけど。

プレコミットフックの使い方の例を教えてくれない? それが他の方法では実行できないロジックをフックで実行するためのものではないから、俺には無理だ。例えば、プレコミットを使って、言語のホワイトスペースフォーマットが適用されていることを強制してる。IDEでは同じ設定だけど、時々開発者はIDEの警告を無視したり、ちょっとした編集のためにテキストエディタでファイルを開いたりして、IDEの警告を見逃すことがある。「CIに置き換えられる」ってのは、俺たちの文脈ではあまり意味がないんだ。プレコミットはCIの一部として動くツールで、いくつかのことは早いからプレコミットフックとして実行される便利な場所なんだ。開発者にはローカルでもプレコミットを実行することが奨励されてるけど、強制はされてない。 > テストは実行してる?どのテストを実行するかどうやって知るの? CIが実行するすべてのテストを走らせるのは遅くなるかもしれないけど。プレコミットフックとプレプッシュフックのパフォーマンスメトリクスがある。正確な数字は忘れたけど、何かが「速く感じる」ことが大事だから、例えば数十のコミットをローカルでリベースしてる場合は数秒で終わるべきなんだ。プレプッシュフックはもう少し余裕がある。

フィードバックの時間と一貫性の問題だね。例えば、CIでPrettier/Ruffを実行すると、誰かが数分待たなきゃいけなくて、ミリ秒では済まない。ビルドの失敗を修正するか、CIシステムにコミット権限を与えてマージコンフリクトに対処しなきゃいけない。これって、誰かのノートパソコンには10個のアイドルコアがあるのに、CIランナーの使用量が増えるってことでもある。プルリクエストやマージリクエストの場合、レビューアの時間を無駄にしてることになる。フックが秘密をブロックしてる場合、100%の確実性でそれをアンプッシュできないから、資格情報を取り消さなきゃいけない。テキストに関しては、「pytest tests/unit/」のようなものを持ってることが多い。これは速くて、特にリファクタリングのような場合に良いサニティチェックになる。俺たちは一貫性のためにCIでもプレコミットチェックを実行してるから、誰かのローカル環境に依存することはないし(ウェブエディタもあるし)、みんなが自分の環境に対して正直でいられるようにしてる。

prekはpre-pushフックをどう扱ってるの?つまり、修正されたファイルのリストをどうやって決定してるの?これはpre-commitの長年の問題点で、詳しくは https://github.com/pre-commit/pre-commit/issues/860 を見てね。リンクされてる重複もあるけど、一部は重複じゃないよ。