一. 前言 #
你有沒有過這種經驗:信心滿滿地 git push,結果 CI 噴了一堆 linting 錯誤,只好再推一個 “fix lint” 的 commit?或是 code review 時被同事指出格式不統一,尷尬到想鑽地洞? 🫠
如果有個工具能在你 git commit 的那一瞬間,自動幫你檢查格式、跑 linter、甚至檢查有沒有不小心把密碼提交上去——那豈不是太完美了?
這就是 pre-commit 框架的用途!它利用 Git 的 hook 機制,在 commit 前自動執行你指定的檢查工具。今天拍拍君就來帶大家從零開始設定 pre-commit,打造一個銅牆鐵壁的開發工作流 💪
二. 什麼是 Git Hooks? #
在介紹 pre-commit 框架之前,先來了解一下 Git hooks。
Git 在特定動作發生時,可以觸發自訂腳本,這些腳本就叫做 hooks。常見的有:
| Hook 名稱 | 觸發時機 | 用途 |
|---|---|---|
pre-commit |
git commit 前 |
檢查程式碼品質 |
commit-msg |
寫完 commit message 後 | 檢查 commit message 格式 |
pre-push |
git push 前 |
跑測試 |
手動管理這些 hooks 很麻煩——每個人要自己設定、不同專案格式不同、更新也是噩夢。所以 pre-commit 框架誕生了,它幫你統一管理所有 hooks!
三. 安裝 #
用 pip 或 uv 安裝都行:
# pip
pip install pre-commit
# 或者用 uv(推薦!)
uv tool install pre-commit
確認安裝成功:
pre-commit --version
# pre-commit 4.1.0
四. 基本設定 #
在你的專案根目錄建立 .pre-commit-config.yaml:
# .pre-commit-config.yaml
repos:
# 一些通用的基本檢查
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace # 移除行尾空白
- id: end-of-file-fixer # 確保檔案結尾有換行
- id: check-yaml # 檢查 YAML 語法
- id: check-added-large-files # 防止提交超大檔案
- id: check-merge-conflict # 檢查是否有未解決的 merge conflict
然後安裝 hooks:
# 在專案目錄下執行
pre-commit install
這個指令會在 .git/hooks/pre-commit 建立一個腳本。之後每次 git commit,都會自動觸發檢查 ✅
來測試一下:
# 故意加一些行尾空白
echo "hello " > test.txt
git add test.txt
git commit -m "test"
你會看到類似這樣的輸出:
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Check for added large files..............................................Passed
Check for merge conflicts................................................Passed
如果有問題,pre-commit 會自動幫你修復(像是移除行尾空白),然後告訴你 Failed。你只需要重新 git add 修復後的檔案,再 commit 一次就好。
五. 加入 Python 專用的 Hooks #
光是基本檢查還不夠,讓我們加入 Python 開發者最愛的工具!
5.1 Ruff — 超快的 Linter + Formatter #
如果你還沒用過 Ruff,可以參考拍拍君之前的文章 👉 Ruff:Python 最速 Linter
# Ruff - 超快速 Python linter & formatter
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.6
hooks:
- id: ruff # linting
args: [--fix] # 自動修復可修復的問題
- id: ruff-format # formatting(取代 black)
5.2 MyPy — 靜態型別檢查 #
# MyPy - 型別檢查
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies: [types-requests] # 需要的 type stubs
args: [--ignore-missing-imports]
💡 如果你的專案用了很多第三方套件,MyPy 可能會比較慢。可以考慮只在 CI 跑,不放在 pre-commit。
5.3 防止密碼洩漏 #
# 偵測不小心提交的密碼、API key
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
第一次使用需要先建立 baseline:
detect-secrets scan > .secrets.baseline
六. 完整範例設定檔 #
這是拍拍君推薦的 Python 專案完整設定:
# .pre-commit-config.yaml
default_language_version:
python: python3.12
repos:
# 通用檢查
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
args: ['--maxkb=1000']
- id: check-merge-conflict
- id: debug-statements # 檢查是否有遺留的 pdb/breakpoint
# Ruff
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.6
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
# Commit message 格式檢查
- repo: https://github.com/commitizen-tools/commitizen
rev: v4.4.1
hooks:
- id: commitizen
七. 實用指令大全 #
手動跑所有檔案 #
# 對所有檔案執行(不只是 staged 的)
pre-commit run --all-files
只跑特定 hook #
pre-commit run ruff --all-files
pre-commit run trailing-whitespace --all-files
更新所有 hooks 到最新版 #
pre-commit autoupdate
這會自動更新 .pre-commit-config.yaml 中的 rev 版本號 🔄
暫時跳過檢查 #
偶爾你可能真的需要跳過檢查(例如緊急 hotfix):
# 跳過所有 hooks
git commit --no-verify -m "emergency fix"
# 跳過特定 hook
SKIP=mypy git commit -m "skip type check this time"
⚠️ 不要養成跳過的習慣!這是緊急逃生口,不是常態。
清除快取 #
pre-commit clean # 清除已下載的 hook 環境
pre-commit gc # 垃圾回收
八. 搭配 CI 雙重保險 #
光靠本機 pre-commit 還是有漏洞——有人可能用 --no-verify 跳過。所以建議在 CI 也跑一次:
# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: pre-commit/action@v3.0.1
這樣就算有人跳過本機的 pre-commit,CI 也會抓到問題 🛡️
九. 進階技巧 #
9.1 自訂 Local Hook #
除了用別人的 repo,你也可以寫自己的 hook:
- repo: local
hooks:
- id: check-todo
name: Check for TODO comments
entry: grep -rn "TODO" --include="*.py"
language: system
pass_filenames: false
always_run: true
9.2 只檢查特定檔案 #
用 files 或 exclude 控制範圍:
- id: mypy
files: ^src/ # 只檢查 src/ 目錄下的檔案
exclude: ^src/legacy/ # 排除 legacy 目錄
9.3 設定 stages #
不同 hook 可以在不同階段執行:
- id: pytest
stages: [pre-push] # 只在 push 前跑測試
記得也要安裝 pre-push hook:
pre-commit install --hook-type pre-push
結語 #
pre-commit 就像是你程式碼的守門員 🧤 ——在壞 code 進入 git 歷史之前,就把它擋下來。
拍拍君覺得,一個好的開發工作流應該是:
- 寫 code → 開心寫
- git add → 選好要提交的
- git commit → pre-commit 自動檢查 ✅
- git push → CI 再跑一次確認 ✅✅
設定一次,受益終生。趕快在你的專案裡加上 pre-commit 吧!🚀
延伸閱讀 #
- pre-commit 官方文件
- Supported Hooks 清單
- Ruff:Python 最速 Linter — 拍拍君之前的文章
- GitHub Actions 自動化 — 搭配 CI 使用