pre-commit 設定 Git Hooks 教學:以 Flake8、isort、yapf 為例

提交前最後一哩

kyo
12 min readApr 29, 2023
from Pixabay

這篇應該算是 Python Linter、Formatter 系列(嚴格來說並沒有這個系列,它們只是彼此相關)的最後一篇,有關「 程式碼整潔工具 」這一主題的文章,之前已經寫了不少,依時間順序如下:

  1. Flake8 與 isort in VS Code
  2. VS Code 設定 Python linter 與 formatter:以 Flake8、yapf 為例(主要)
  3. 試用從 VS Code Python extension 拆分的 Black、isort 套件
  4. pyproject.toml 介紹 + VS Code 整合 Black、yapf、isort 教學
  5. VS Code:Python isort 擴充套件介紹與簡易設定教學
  6. 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-pushpost-merge 等等,管理 pre-commit hook 只是其中最常見的用途而已,我想這應該不至於造成誤會。

為什麼要使用 pre-commit?

這個問題可以分為 兩個層次

  1. 為什麼要使用 pre-commit hook?
  2. 為什麼要使用 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-commitpip 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檔中的reporevhooksid等設定,直接看文件就知道意思了。

設定上重點

對於第一次接觸 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.

--

--

kyo

這裡只更新「非程式相關」文章,更多內容歡迎查看我的部落格:https://blog.kyomind.tw/ ;如果你只對非程式類文章感興趣,歡迎訂閱🥰