pre-commit 設定 Git Hooks 教學:以 Flake8、isort、yapf 為例
這篇應該算是 Python Linter、Formatter 系列(嚴格來說並沒有這個系列,它們只是彼此相關)的最後一篇,有關「 程式碼整潔工具 」這一主題的文章,之前已經寫了不少,依時間順序如下:
- Flake8 與 isort in VS Code
- VS Code 設定 Python linter 與 formatter:以 Flake8、yapf 為例(主要)
- 試用從 VS Code Python extension 拆分的 Black、isort 套件
- pyproject.toml 介紹 + VS Code 整合 Black、yapf、isort 教學
- VS Code:Python isort 擴充套件介紹與簡易設定教學
- VS Code:Python Flake8 與 Black Formatter 擴充套件快速上手
仔細一看,其實不就是 Flake8、yapf、isort、Black Formatter 這 4 項工具的排列組合而已嗎XD。
是的!我自己整理到都覺得有點不好意思了🫣。
但是,為什麼要寫這麼多?還是那句老話:「 我認為這些工具的重要性,再怎麼強調也不為過 — 更別說它們常常被低估了。 」
我曾在上述文章中的開頭或結尾處,不厭其煩地表達自己對它們的重視,這裡就不再贅述,直接進入重點。
本文主旨
在這篇文章中,我們將講述如何使用 pre-commit 設定 Git Hooks,並以 Flake8、isort、yapf 為例來進行演示,同時提醒一些注意事項。
如同 Poetry,pre-commit 也是我在工作上,強烈希望導入的工具 — — 畢竟它可以有效降低我 code review 時血壓升高的次數☺️
本文架構
我們會先講一下什麼是 Git Hooks 和 pre-commit,因為不是論述上的重點,所以只需簡單帶過,讀者心中有個輪廓即可。
接著探討「 為什麼要使用 pre-commit 工具? 」。和我過去的許多文章一樣,對動機與場景將有一定程度的著墨,我們會探討 pre-commit 的價值所在,包括我從團隊使用經驗得到的看法。
最後是具體設定 — 這或許是最不重要的部分,因為它很簡單,但還是有一些值得留意的部分。以 Flake8、isort、yapf 為例,則是為了貫徹前述所有文章的一貫精神: 有了 pre-commit,這一切才算圓滿。
附帶一提,考慮到「pre-commit」這個主題已經有數篇文章珠玉在前(參考文章我會放在文末),我下筆前都已拜讀完。本文的論述重點會 盡可能與這些文章錯開,或乾脆直接引用 ,以降低不必要的重複感。
Git Hooks 介紹
Git Hooks 是 Git 提供的一個功能,允許開發者在特定的 Git 事件(如 commit、push 等)觸發時,自動執行自定義的腳本,這樣可以在提交程式碼之前進行一些檢查,以確保程式碼的交付品質。
Git Hooks 有很多種,不同的 Git Hooks 可以幫助開發者在不同的場景下進行自動化操作,這裡我們只要知曉 最常用的 pre-commit hook 已足。
顧名思義,pre-commit hook 會在你使用 git commit
命令時觸發,讓機器依照你的 hook 腳本內容,在 commit 前對程式碼進行檢查。
事實上,你可以用任何語言來撰寫 hook 腳本,只要編寫的腳本能夠在系統上被執行,而且符合 Git hook 的腳本格式,就可以使用。
開發者只需要在.git/hooks
目錄下建立相對應的 hook(命名必須遵守規則),編寫自己需要的處理邏輯即可。
但我們才不會這麼做!因為我們有工具嘛!
pre-commit 工具介紹
即使你會寫 shell script,也不一定想自己手刻一個 hook 腳本,所幸這麼常見的需求,自然有許多開源的工具可以使用,而 pre-commit 就是其中的佼佼者 — 只能說這個套件的命名非常簡單暴力!🤣
pre-commit 是由 Python 所寫成的 Git Hooks 管理工具,但可以適用於各類程式語言專案,包括 Python、JavaScript、Ruby、Java、Go 等。使用者可以根據自己的需求,選擇要使用的 hook。
雖然叫 pre-commit,但實際上它可以管理全部種類的 Git Hooks,比如 pre-push
、 post-merge
等等,管理 pre-commit hook 只是其中最常見的用途而已,我想這應該不至於造成誤會。
為什麼要使用 pre-commit?
這個問題可以分為 兩個層次 :
- 為什麼要使用 pre-commit hook?
- 為什麼要使用 Git Hooks 自動化工具?
這兩個問題有先後關係,先有 1 才有 2。不過 2 的理由比較簡單,所以我們先講 2 再講 1。
為什麼要使用自動化工具來管理 Git Hooks?
除了和〈 是時候同步你的 dotfiles 了 — 我選擇 yadm〉一樣: 因為這個方式更加輕鬆 以外。更關鍵的是,透過工具你可以像堆樂高積木般,輕鬆組合各式套件所提供的現成 hook,比如本文的 Flake8,這顯然比自己寫 shell 要高效得多。
所以比起管理 dotfiles,直接透過工具來管理 Git Hooks,應該是絕大多數人 自然而然的選擇 。
為什麼要使用 pre-commit hook?
在〈 提升程式碼品質:使用 Pre-Commit (Git Hooks) 〉一文中,作者提出了三點,闡述為何要使用 pre-commit hook,理由充分且令人信服,請容我直接引用:
- 自動化檢查程式碼排版規範快速又有效率(如 python PEP8)
- 低級的問題不會進到 code review
- 多一點時間檢查程式邏輯,而不是基本錯誤(如排版)。
- 人工檢查程式碼的時間很寶貴,減少人工即是增進效率。
- 低級的問題不會進到 CI/CD pipeline
- pipeline 應該多一點綠勾勾,而不是滿滿 debug 的痕跡。
我的關注點
此外,我在臉書社團「Backend 台灣 (Backend Tw)」 這篇文章 的回應中,也表達了自己的看法,這裡只節錄最後兩段:
上面的情況其實都在說:「linter已經提醒有問題,但formatter無法正常格式化」,細心的人會停下來排除問題或討論,但人難免都有粗心的時候,偶爾還是忽略且commit了
負責 review 的人看到這種格式類的錯誤,心裡難免會有點無言,但又不好直接怪隊友,畢竟對方也不是「故意」的,此時有機器幫彼此擋一下,也算是團隊協作的潤化劑
是的,我最大的著眼點在於:「 減少團隊協作時,不必要的磨擦(精神消耗)。 」
個人認為這真的很重要,畢竟我們不是機器,都有情緒,而情緒應該要用在更有價值的地方。
小結
總的來說,如果你問我「為何要使用 pre-commit?」從個人角度看,我已視之為理所當然,畢竟我都寫了 6 篇相關文章了 — pre-commit 正是確保這些 linter、formatter 能夠發揮全部效用的最後一塊拼圖 !
而從團隊的角度,則是因為「這樣對大家都好!」
pre-commit 基本設定
這是所有文章都會提到的部分,所以我們可以加快一點。
安裝 pre-commit 應該不必解釋, poetry add pre-commit
或 pip install pre-commit
任君挑選。macOS 也可以用 brew install pre-commit
全域安裝,但如此一來它就不會在專案虛擬環境中。
.pre-commit-config.yaml
接著是關於.pre-commit-config.yaml
的編輯,請將此檔案建立於專案的根目錄,pre-commit 就是透過這份設定檔,實現各項 hook 內容。
一般而言,.pre-commit-config.yaml
會長這樣:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: check-added-large-files
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
最上面是 由 pre-commit 專案提供的基本 hooks 。剩下的部分則是其它開源專案所提供的 hook。
yaml
檔中的repo
、rev
、hooks
、id
等設定,直接看文件就知道意思了。
設定上重點
對於第一次接觸 pre-commit 的人而言,這裡會有的最大疑問應該是:「啊我怎麼知道我可以用哪些 hook?我又怎麼知道這些 hook 的 repo 網址?」
沒錯,這是關鍵所在,這個網頁有目前全部可用的 hook 清單,上面如果沒有列出你想用的工具,那就是暫不支援囉!
至於rev
的具體版本號(總不能自己瞎編一個),repo 網頁的「tags」頁面就會有,比如這是 Flake8 的 tags 頁面。
值得留意的是:有些 hook 的 tag 有「v
」開頭,有些則否(上述 Flake8 就沒有)。這基本上看專案開發者的習慣,不要搞錯即可。
知道這些,基本上就能設定好.pre-commit-config.yaml
了。
pre-commit 設定 Flake8、isort、yapf
又到了整個「系列」都不斷重複、重複、再重複的幾樣工具😎
Flake8
直接看設定檔:
repos:
...
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
exclude: migrations/
args:
- --max-line-length=100
- --ignore=E131,E126,E402
hooks
設定下所有可以用的子項目, 上述的頁面 已有說明。
這只需要提醒一件事:那就是 args
的設定內容 務必和編輯器(比如 VS Code)的設定一致。
換句話說,我 VS Code 的 settings.py
也有一樣的設定:
"flake8.args": [
"--max-line-length=100",
"--ignore=E131,E126,E402"
],
如果兩者的規則不同,恐怕會發生 意料之外的結果 。
isort
repos:
...
- repo: https://github.com/PyCQA/isort
rev: 5.11.5
hooks:
- id: isort
exclude: migrations/
args:
- --line-length=100
一樣特別注意 args
,尤其像 isort、yapf 等「 formatter」,設定不一致會直接導致在編輯器中格式化符合規範,但 commit 時卻被 hook 擋下來的窘境 。
yapf
repos:
...
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.32.0
hooks:
- id: yapf
exclude: migrations/
args:
- column_limit = 100
additional_dependencies: [toml]
yapf 有一個 很常見的問題,之前在〈 pyproject.toml 介紹 + VS Code 整合 Black、yapf、isort 教學 〉中已提過。
簡言之,只要你的專案中有 pyproject.toml
,無論裡面是否有關於 yapf 的設定,yapf 都會試圖去解析它,此時 toml
套件就成了必不可少的一環。如果沒有 toml
,yapf 便會無法正常運作。
這個問題在 pre-commit 同樣會發生,所以如果專案中有 pyproject.toml
,就要額外設定 additional_dependencies: [toml]
,方能功德圓滿🙏
最後一哩
附上完整的.pre-commit-config.yaml
供參:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: check-added-large-files
- id: check-ast
- id: check-case-conflict
- id: check-json
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
exclude: migrations/
args:
- --max-line-length=100
- --ignore=E131
- repo: https://github.com/PyCQA/isort
rev: 5.11.5
hooks:
- id: isort
exclude: migrations/
args:
- --line-length=100
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.32.0
hooks:
- id: yapf
exclude: migrations/
args:
- column_limit = 100
additional_dependencies: [toml]
還有一件想當然爾但依舊必須提醒的事,即 hook 使用的 rev
版本,要和你專案裡使用的工具的版本一致。比如上述設定檔中我的 Flake8 是 5.0.4
版,那虛擬環境裡安裝的 flake8
自然也是 5.0.4
,而不會是其它版本。
安裝完 pre-commit 並建立好.pre-commit-config.yaml
後, 事情還沒有結束!此時專案中的.git/hooks
目錄下,依舊是預設時的模樣。
pre-commit install
我們還差一個動作 — 將.pre-commit-config.yaml
轉譯為 hook 腳本,當然,這會由工具代勞,我們只需要下指令即可:
pre-commit install
下指令時請確定虛擬環境已經開啟,不然可能會找不到 pre-commit
這個執行檔--除非你是全域安裝。
這個指令還有相關參數可以使用,有興趣可參考文件 。不過在絕大多數情況下,這樣就已經足夠。
參考文章
Originally published at https://blog.kyomind.tw on April 29, 2023.