GitHook 工具 —— husky介紹及使用

名稱

githooks-Git使用的掛鉤。(githook在官網的介紹)node

描述

如同其餘許多的版本控制系統同樣,Git 也具備在特定事件發生以前或以後執行特定腳本代碼功能(從概念上類比,就與監聽事件、觸發器之類的東西相似)。Git Hooks 就是那些在Git執行特定事件(如commit、push、receive等)後觸發運行的腳本,掛鉤是能夠放置在掛鉤目錄中的程序,可在git執行的某些點觸發動做。沒有設置可執行位的鉤子將被忽略。python

默認狀況下,hooks目錄是$GIT_DIR/hooks,可是能夠經過core.hooksPath配置變量來更改(請參見 git-config [1])。git

Git Hooks 能作什麼

Git Hooks是定製化的腳本程序,因此它實現的功能與相應的git動做相關,以下幾個簡單例子:
1.多人開發代碼語法、規範強制統一
2.commit message 格式化、是否符合某種規範
3.若是有須要,測試用例的檢測
4.服務器代碼有新的更新的時候通知全部開發成員
5.代碼提交後的項目自動打包(git receive以後) 等等...github

更多的功能能夠按照生產環境的需求寫出來shell

Git Hooks 是如何工做的

每個使用了 git 的工程下面都有一個隱藏的 .git 文件夾。
npm

掛鉤都被存儲在 .git 目錄下的 hooks 子目錄中,即大部分項目中的 .git/hooks。 以下圖:
編程

Git 默認會放置一些腳本樣本在這個目錄中,除了能夠做爲掛鉤使用,這些樣本自己是能夠獨立使用的。全部的樣本都是shell腳本,其中一些還包含了Perl的腳本。不過,任何正確命名的可執行腳本均可以正常使用 ,也能夠用Ruby或Python,或其餘腳本語言。json

上圖是git 初始化的時候生成的默認鉤子,已包含了大部分可使用的鉤子,可是 .sample 拓展名防止它們默認被執行。爲了安裝一個鉤子,你只須要去掉 .sample 拓展名。或者你要寫一個新的腳本,你只需添加一個文件名和上述匹配的新文件,去掉.sample拓展名。把一個正確命名且可執行的文件放入 Git 目錄下的 hooks子目錄中,能夠激活該掛鉤腳本,以後他一直會被 Git 調用。數組

一個簡單的 Hooks 例子

使用shell 這裏嘗試寫一個簡單的鉤子,安裝一個prepare-commit-msg鉤子。去掉腳本的.sample拓展名,在文件中加上下面這兩行:緩存

#!/bin/sh

echo "# Please include a useful commit message!" > $1

接下來你每次運行git commit時,你會看到默認的提交信息都被替換了。

內置的樣例腳本是很是有用的參考資料,由於每一個鉤子傳入的參數都有很是詳細的說明(不一樣鉤子不同)。

腳本語言

git本身生成的默認鉤子的腳本大可能是shell和Perl語言的,但你可使用任何腳本語言,只要它們最後能編譯到可執行文件。每次腳本中的 #!/bin/sh 定義了你的文件將被如何解析。好比,使用其餘語言時你只須要將path改成你的解釋器的路徑。

好比說,你能夠在 prepare-commit-msg 中寫一個可執行的Python腳本。下面這個鉤子和上一節的shell腳本作的事徹底同樣。

#!/usr/bin/env python

import sys, os

commit_msg_filepath = sys.argv[1]
with open(commit_msg_filepath, 'w') as f:
    f.write("# Please include a useful commit message!")

注意第一行改爲了python解釋器的路徑。此外,這裏用sys.argv[1]而不是$1來獲取第一個參數。這個特性很是強大,由於你能夠用任何你喜歡的語言來編寫Git鉤子。

鉤子的做用域

對於任何Git倉庫來講鉤子都是本地的,並且它不會隨着git clone一塊兒複製到新的倉庫。並且,由於鉤子是本地的,任何能接觸獲得倉庫的人均可以修改。在開發團隊中維護鉤子是比較複雜的,由於.git/hooks目錄不隨你的項目一塊兒拷貝,也不受版本控制影響。一個簡單的解決辦法是把你的鉤子存在項目的實際目錄中(在.git外)。這樣你就能夠像其餘文件同樣進行版本控制。

做爲備選方案,Git一樣提供了一個模板目錄機制來更簡單地自動安裝鉤子。每次你使用 git initgit clone時,模板目錄文件夾下的全部文件和目錄都會被複制到.git文件夾。

HOOKS(鉤子)的幾種狀況 (這一節官網是翻譯,能夠不用仔細看)

1.applypatch-msg(應用程序消息)

這個鉤子由git am調用。它只有一個參數,即保存建議的提交日誌消息的文件的名稱。以非零狀態退出會致使git am在應用補丁以前停止。

該掛鉤容許在適當位置編輯消息文件,並可用於將消息規範化爲某些項目標準格式。檢查消息文件後,它也能夠用於拒絕提交。

啓用後,默認的applypatch-msg掛鉤將運行 commit-msg掛鉤(若是後者已啓用)。

2.pre-applypatch(應用前批處理)

這個鉤子由git am調用。它不接受任何參數,並在應用補丁程序以後、提交以前調用。

若是它以非零狀態退出,則在應用補丁程序後將不會提交工做樹。

它能夠用來檢查當前的工做樹,若是不經過某些測試,則拒絕提交。

默認的pre-applypatch鉤子在啓用時運行pre-commit鉤子(若是後者已啓用)。

3.post-applypatch(應用程序批處理後)
這個鉤子由git am調用。它不接受任何參數,在應用補丁程序並提交以後調用。

這個鉤子主要用於通知,不能影響git am的結果。

4.pre-commit(預先提交)
這個鉤子由git commit調用,可使用--no-verify選項繞過它。它不接受任何參數,並在獲取建議的提交日誌消息和進行提交以前被調用。從這個腳本中退出非零狀態會致使git commit命令在建立提交以前停止。

默認的pre-commit掛鉤(若是啓用)捕獲帶有尾隨空白的行的引入,並在找到此類行時停止提交。

若是命令不會打開編輯器來修改提交消息,則使用環境變量 GIT_EDITOR=: 調用全部git commit掛鉤。

當啓用hooks.allownonascii配置選項unset或設置爲false時,默認的pre-commit掛鉤將阻止使用非ASCII文件名。

5.pre-merge-commit(合併前提交)
這個鉤子由git merge[1]調用,可使用--no-verify選項繞過它。它不接受任何參數,並在合併成功執行以後和獲取建議的提交日誌消息以進行提交以前調用。從這個腳本中退出非零狀態會致使Git合併命令在建立提交以前停止。

若是啓用了pre-merge-commit掛鉤,則默認的預合併提交掛鉤將運行pre-commit掛鉤。

若是命令不會調出編輯器來修改提交消息,則使用環境變量GIT_EDITOR=:調用此掛鉤。

若是沒法自動執行合併,則須要解決衝突並單獨提交結果(參見git merge)。此時,將不會執行此掛鉤,但若是啓用了pre-commit掛鉤,則會執行它。

6.prepare-commit-msg(準備提交消息)
git commit在準備默認日誌消息以後,在啓動編輯器以前調用此鉤子。

它須要一到三個參數。第一個是包含提交日誌消息的文件的名稱。第二個是提交消息的來源,能夠是:message(若是給出了-m-F選項);template(若是給出了-t選項或配置選項commit.template);merge(若是提交是合併或.git/MERGE_MSG文件);squash(若是.git/SQUASH_MSG文件存在);或commit,接着是提交SHA-1(若是是-c-C)或者--amend 選項)。

若是退出狀態爲非零,則git commit將停止。

鉤子的目的是就地編輯消息文件,而--no-verify選項不由止它。非零退出意味着鉤子失敗,並停止提交。它不該該用做預提交掛鉤的替換。

Git附帶的prepare-commit-msg鉤子示例刪除了commit模板註釋部分中的幫助消息。

7.commit-msg(提交信息)
這個鉤子由git commitgit merge調用,可使用--no-verify選項繞過它。它接受一個參數,即保存建議的提交日誌消息的文件的名稱。退出非零狀態會致使命令停止。

容許鉤子就地編輯消息文件,並可用於將消息規範化爲某些項目標準格式。它還可用於在檢查消息文件後拒絕提交。

默認的commit-msg hook在啓用時檢測到重複的Signed-off-by行,若是找到一行,則停止提交。

8.post-commit(提交後)
這個鉤子由git commit調用。它不接受任何參數,並在提交後調用。

這個鉤子主要用於通知,不能影響git commit的結果。

9.pre-rebase(變基前)
這個鉤子由git rebase調用,可用於防止分支從新定位。可使用一個或兩個參數調用鉤子。第一個參數是派生序列的上游。第二個參數是正在重設基的分支,重設基當前分支時不設置該參數。

10.post-checkout(結帳後)
更新工做樹後運行git checkoutgit switch時,將調用此掛鉤。鉤子有三個參數:前一個HEAD的ref,新HEAD的ref(可能已經更改,也可能沒有更改)和一個標誌,指示簽出是分支簽出(更改分支,flag=1)仍是文件簽出(從索引中檢索文件,flag=0)。此掛鉤不會影響git switchgit checkout的結果。

它也在git clone[1]以後運行,除非使用--no-checkout-n)選項。給鉤子的第一個參數是空ref,第二個參數是新頭的ref,標誌老是1。一樣,對於git worktree add,除非--no-checkout簽出。

此鉤子可用於執行存儲庫有效性檢查、自動顯示與前一個HEAD的差別(若是不一樣)或設置工做目錄元數據屬性。

11.post-merge(合併後)
這個鉤子由git merge調用,當在本地存儲庫上完成git pull時就會發生這種狀況。鉤子接受一個參數,一個狀態標誌,指定正在進行的合併是不是擠壓合併。若是合併因爲衝突而失敗,則此掛鉤不會影響git merge的結果,也不會執行。

此鉤子可與相應的預提交鉤子結合使用,以保存和還原與工做樹相關聯的任何形式的元數據(例如:permissions/ownership, ACLS等)。請參閱contrib/hooks/setgitperms.perl,以獲取如何執行此操做的示例。

12.pre-push(預推)
這個鉤子被git push調用,能夠用來防止發生push。使用兩個參數調用鉤子,這兩個參數提供目標遠程的名稱和位置,若是未使用命名遠程,則兩個值將相同。

有關要推送的內容的信息在鉤子的標準輸入中提供,輸入行以下:

<local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF

例如,若是運行git push origin master:foreign命令,鉤子將收到以下行:

refs/heads/master 67890 refs/heads/foreign 12345

儘管將提供完整的、40個字符的SHA-1。若是外部參考還不存在,<remote SHA-1> 將是40 0。若是要刪除引用,<local ref>將做爲(delete)提供,<remote SHA-1>將爲40 0。若是本地提交不是由可擴展的名稱(如HEAD~SHA-1)指定的,則將按最初的給定方式提供。

若是這個鉤子退出非零狀態,git push將停止而不推任何東西。有關拒絕推送的緣由的信息能夠經過寫入標準錯誤發送給用戶。

13.pre-receive(預先接收)
git-receive-pack對其存儲庫中的git push和updates引用做出反應時,它將調用此鉤子。在開始更新遠程存儲庫上的refs以前,將調用預接收掛鉤。它的退出狀態決定了更新的成功或失敗。

對於接收操做,此鉤子執行一次。它不須要參數,可是對於每一個要更新的ref,它在標準輸入上接收一行格式:

<old-value> SP <new-value> SP <ref-name> LF

其中,<old-value>是存儲在ref中的舊對象名,<new-value>是存儲在ref中的新對象名,<ref-name>是ref的全名。建立新ref時,<old-value>是40 0

若是鉤子退出非零狀態,則不會更新任何參考文件。若是鉤子以0退出,則更新鉤子仍然能夠防止單個引用的更新。

標準輸出和標準錯誤輸出都被轉發到另外一端的git send-pack,所以您能夠簡單地爲用戶回顯消息。

git push命令行中給定的push選項數--push-option=... 能夠從環境變量GIT_PUSH_OPTION_COUNT中讀取,選項自己位於GIT_PUSH_OPTION_0GIT_PUSH_OPTION_1,…中。若是協商不使用PUSH options階段,則不會設置環境變量。若是客戶端選擇使用push選項,但不傳輸任何選項,則count變量將設置爲零,GIT_push_OPTION_count=0

有關一些注意事項,請參閱git-receive-pack中關於「隔離環境」的部分。

14.update(更新)
git-receive-pack對其存儲庫中的git push和updates引用做出反應時,它將調用此鉤子。就在更新遠程存儲庫上的ref以前,會調用更新掛鉤。它的退出狀態決定了REF更新的成敗。

鉤子對每一個要更新的ref執行一次,並接受3個參數:

  • 正在更新的ref的名稱,
  • 存儲在ref中的舊對象名,
  • 以及要存儲在ref中的新對象名。

從更新鉤子的零出口容許REF被更新。退出非零狀態阻止git receive-pack更新REF。

經過確保對象名是commit對象(commit對象是由舊對象名命名的commit對象的後代),此鉤子可用於防止強制更新某些ref。也就是說,執行「僅限快進」政策。

它還能夠用來記錄舊的..新的狀態。可是,它不知道整個分支集,所以在天真地使用時,它最終會爲每一個ref觸發一封電子郵件。post-receive鉤子更適合這種狀況。

在一個僅限制用戶經過網絡訪問git命令的環境中,此鉤子可用於實現訪問控制,而不依賴文件系統全部權和組成員資格。請參閱git shell瞭解如何使用登陸shell限制用戶僅訪問git命令。

標準輸出和標準錯誤輸出都被轉發到另外一端的git send-pack,所以您能夠簡單地爲用戶回顯消息。

默認的update hook在啓用時,若是hooks.allowunannotated config選項未設置或設置爲false,則會阻止推送未註釋的標記。

15.post-receive(接收後)
git-receive-pack對其存儲庫中的git push和updates引用做出反應時,它將調用此鉤子。在更新全部ref以後,它在遠程存儲庫上執行一次。

對於接收操做,此鉤子執行一次。它不接受參數,但獲取的信息與pre-receive鉤子在其標準輸入上所作的相同。

這個鉤子不會影響git receive-pack的結果,由於它是在實際工做完成後調用的。

這將取代post-update掛鉤,由於它除了獲取全部ref的名稱外,還獲取它們的舊值和新值。

標準輸出和標準錯誤輸出都被轉發到另外一端的git send-pack,所以您能夠簡單地爲用戶回顯消息。

默認的post-receive鉤子是空的,可是Git發行版的contrib/hooks目錄中提供了一個示例腳本post-receive email,它實現了發送提交電子郵件。

git push命令行中給定的push選項數--push-option=...能夠從環境變量GIT_PUSH_OPTION_COUNT中讀取,選項自己位於GIT_PUSH_OPTION_0GIT_PUSH_OPTION_1,…中。若是協商不使用PUSH options階段,則不會設置環境變量。若是客戶端選擇使用push選項,但不傳輸任何選項,則count變量將設置爲零,GIT_push_OPTION_count=0

16.post-update(更新後)
git-receive-pack對其存儲庫中的git push和updates引用做出反應時,它將調用此鉤子。在更新全部ref以後,它在遠程存儲庫上執行一次。

它接受可變數量的參數,每一個參數都是實際更新的ref的名稱。

此鉤子主要用於通知,不能影響git receive-pack的結果。

post-update鉤子能夠告訴推送的頭是什麼,可是它不知道它們的原始值和更新值是什麼,因此它是一個很糟糕的地方來記錄舊的..新的。post-receive鉤子獲取refs的原始值和更新值。若是你須要的話,你能夠考慮一下。

啓用後,默認的post-update掛鉤運行git update-server-info 以保持dumb transports(例如HTTP)使用的信息是最新的。若是您要發佈一個能夠經過HTTP訪問的Git存儲庫,那麼您可能應該啓用這個鉤子。

標準輸出和標準錯誤輸出都被轉發到另外一端的git send-pack,所以您能夠簡單地爲用戶回顯消息。

17.push-to-checkout(推送至結賬)
git-receive-pack對其存儲庫中的git push和update s引用做出反應,而且當push嘗試更新當前簽出的分支而且receive.denyCurrentBranch配置變量設置爲updateInstead 時,它將調用此鉤子。若是工做樹和遠程存儲庫的索引與當前簽出的提交有任何差別,則默認狀況下拒絕此類推送;當工做樹和索引都與當前提交匹配時,它們將更新以匹配分支的新推送提示。此鉤子將用於重寫默認行爲。

鉤子接收當前分支的提示將被更新的提交。它能夠以非零狀態退出拒絕推送(當它這樣作時,它沒必要修改索引或工做樹)。或者,噹噹前分支的尖端被更新爲新的提交,並以零狀態退出時,它能夠對工做樹和索引進行任何須要的更改,以使它們達到所但願的狀態。

例如,鉤子能夠簡單地運行git read-tree -u -m HEAD "$1",以模擬git push反向運行的git fetch,由於git read tree -u -m的兩種樹形式本質上與git switchgit checkout相同,後者切換分支,同時保持工做樹中不干擾的本地更改樹枝之間的差異。

18.pre-auto-gc(前自動gc)
這個鉤子由git gc --auto調用(參見git gc)。它不須要任何參數,而且從這個腳本中退出非零狀態,致使git gc --auto停止。

19.post-rewrite(重寫後)
此鉤子由重寫提交的命令調用(使用--amendgit rebase調用git commit;可是,git fast-importgit filter-repo之類的完整歷史(從新)編寫工具一般不會調用它!)。它的第一個參數表示它被調用的命令:當前是amend rebase之一。未來可能會傳遞更多依賴命令的參數。

鉤子接收stdin上重寫的提交列表,格式以下

<old-sha1> SP <new-sha1> [ SP <extra-info> ] LF

extra-info一樣依賴於命令。若是爲空,則前面的SP也將被忽略。目前,沒有命令傳遞任何extra-info

鉤子老是在自動複製便箋以後運行(參見git config中的「notes.rewrite.<command>」),所以能夠訪問這些便箋。

如下命令特定註釋適用:
rebase
對於squashfixup操做,全部擠壓的提交都將被列爲被重寫爲擠壓的提交。這意味着將有多條線路共享同一個new-sha1
保證提交按rebase處理的順序列出。

20.sendemail-validate(發送電子郵件驗證)
此鉤子由git send-email[1]調用。它只接受一個參數,即保存要發送的電子郵件的文件的名稱。退出非零狀態致使git send-email在發送任何電子郵件以前停止。

21.fsmonitor-watchman(監聽看守者)
當配置選項core.fsmonitor設置爲.git/hooks/fsmonitor-watchman時,將調用此鉤子。它須要兩個參數,一個版本(當前爲1)和自1970年1月1日午夜以來以納秒爲單位的時間。

鉤子應該輸出到stdout工做目錄中自請求時間以來可能已更改的全部文件的列表。邏輯應該是包含的,這樣就不會遺漏任何潛在的更改。這些路徑應該相對於工做目錄的根目錄,並由單個NUL分隔。

能夠包含沒有實際更改的文件。應包括全部更改,包括新建立和刪除的文件。重命名文件時,應同時包含舊名稱和新名稱。

Git將根據給定的路徑名限制它檢查哪些文件進行更改,以及檢查哪些目錄以查找未跟蹤的文件。

告訴git「全部文件都已更改」的一種優化方法是返回filename/

退出狀態決定Git是否使用鉤子中的數據來限制其搜索。出錯時,它將返回到驗證全部文件和文件夾。

22.p4-pre-submit(p4預先提交)
此鉤子由git-p4 submit調用。它不接受任何參數,也不接受標準輸入。從腳本中退出非零狀態,防止git-p4 submit從啓動提交。運行git-p4 submit --help獲取詳細信息。

23.post-index-change(索引後變化)
當索引寫入讀緩存時調用此掛鉤。c do_write_locked_index。

傳遞給鉤子的第一個參數是正在更新的工做目錄的指示符。「1」表示工做目錄已更新,或「0」表示工做目錄未更新。

傳遞給鉤子的第二個參數是指示索引是否已更新以及跳過工做樹位是否已更改的指示器。」「1」表示跳過工做樹位可能已更新,「0」表示它們未更新。

鉤子運行時,只有一個參數應設置爲「1」。吊鉤不該經過「1」、「1」。

經常使用鉤子有哪些

就像上面說的,那麼多鉤子咱們不是都會用到,下面就介紹幾個常常用到的鉤子,舉例說明一下。

客戶端 Hooks

客戶端鉤子隻影響它們所在的本地倉庫。有許多客戶端掛鉤,如下把他們分爲:提交工做流掛鉤、電子郵件工做流掛鉤及其餘客戶端掛鉤。

1.提交工做流掛鉤

commit操做有 4個掛鉤被用來處理提交的過程,他們的觸發時間順序以下:
pre-commitprepare-commit-msgcommit-msgpost-commit

pre-commit
pre-commit 掛鉤在鍵入提交信息前運行,最早觸發運行的腳本。被用來檢查即將提交的代碼快照。例如,檢查是否有東西被遺漏、運行一些自動化測試、以及檢查代碼規範。當從該掛鉤返回非零值時,Git 放棄這次提交,但能夠用git commit --no-verify來忽略。該掛鉤能夠被用來檢查代碼錯誤,檢查代碼格式規範,檢查尾部空白(默認掛鉤是這麼作的),檢查新方法(譯註:程序的函數)的說明。

pre-commit 不須要任何參數,以非零值退出時將放棄整個提交。這裏,咱們用 「強制代碼格式校驗」 來講明。

prepare-commit-msg
prepare-commit-msg 掛鉤在提交信息編輯器顯示以前,默認信息被建立以後運行,它和 pre-commit 同樣,以非零值退出會放棄提交。所以,能夠有機會在提交做者看到默認信息前進行編輯。該掛鉤接收一些選項:擁有提交信息的文件路徑,提交類型。例如和提交模板配合使用,以編程的方式插入信息。提交信息模板的提示修改在上面已經看到了,如今咱們來看一個更有用的腳本。在處理須要單獨開來的bug時,咱們一般在單獨的分支上處理issue。若是你在分支名中包含了issue編號,你可使用prepare-commit-msg鉤子來自動地將它包括在那個分支的每一個提交信息中。

#!/usr/bin/env python

import sys, os, re
from subprocess import check_output

# 收集參數
commit_msg_filepath = sys.argv[1]
if len(sys.argv) > 2:
    commit_type = sys.argv[2]
else:
    commit_type = ''
if len(sys.argv) > 3:
    commit_hash = sys.argv[3]
else:
    commit_hash = ''

print "prepare-commit-msg: File: %s\nType: %s\nHash: %s" % (commit_msg_filepath, commit_type, commit_hash)

# 檢測咱們所在的分支
branch = check_output(['git', 'symbolic-ref', '--short', 'HEAD']).strip()
print "prepare-commit-msg: On branch '%s'" % branch

# 用issue編號生成提交信息
if branch.startswith('issue-'):
    print "prepare-commit-msg: Oh hey, it's an issue branch."
    result = re.match('issue-(.*)', branch)
    issue_number = result.group(1)

    with open(commit_msg_filepath, 'r+') as f:
        content = f.read()
        f.seek(0, 0)
        f.write("ISSUE-%s %s" % (issue_number, content))

首先,上面的prepare-commit-msg 鉤子告訴你如何收集傳入腳本的全部參數。接下來,它調用了git symbolic-ref --short HEAD 來獲取對應HEAD的分支名。若是分支名以issue-開頭,它會重寫提交信息文件,在第一行加上issue編號。好比你的分支名issue-224,下面的提交信息將會生成:

ISSUE-224 

# Please enter the commit message for your changes. Lines starting 
# with '#' will be ignored, and an empty message aborts the commit. 
# On branch issue-224 
# Changes to be committed: 
# modified:   test.txt

有一點要記住的是即便用戶用-m傳入提交信息,prepare-commit-msg也會運行。也就是說,上面這個腳本會自動插入ISSUE-[#]字符串,而用戶沒法更改。你能夠檢查第二個參數是不是提交類型來處理這個狀況。可是,若是沒有-m選項,prepare-commit-msg鉤子容許用戶修改生成後的提交信息。因此這個腳本的目的是爲了方便,而不是推行強制的提交信息規範。若是你要這麼作,你須要下面所講的commit-msg鉤子。

commit-msg
commit-msg鉤子和prepare-commit-msg鉤子很像,但它會在用戶輸入提交信息以後被調用。這適合用來提醒開發者他們的提交信息不符合你團隊的規範。傳入這個鉤子惟一的參數是包含提交信息的文件名。若是它不喜歡用戶輸入的提交信息,它能夠在原地修改這個文件(和prepare-commit-msg同樣),或者它會以非零值退出,放棄這個提交。好比說,下面這個腳本確認用戶沒有刪除prepare-commit-msg腳本自動生成的ISSUE-[#]字符串。

#!/usr/bin/env python

import sys, os, re
from subprocess import check_output

# 收集參數
commit_msg_filepath = sys.argv[1]

# 檢測所在的分支
branch = check_output(['git', 'symbolic-ref', '--short', 'HEAD']).strip()
print "commit-msg: On branch '%s'" % branch

# 檢測提交信息,判斷是不是一個issue提交
if branch.startswith('issue-'):
    print "commit-msg: Oh hey, it's an issue branch."
    result = re.match('issue-(.*)', branch)
    issue_number = result.group(1)
    required_message = "ISSUE-%s" % issue_number

    with open(commit_msg_filepath, 'r') as f:
        content = f.read()
        if not content.startswith(required_message):
            print "commit-msg: ERROR! The commit message must start with '%s'" % required_message
            sys.exit(1)

post-commit
post-commit 掛鉤在整個提交過程完成後運行,他不會接收任何參數,但能夠運行git log來得到最後的提交信息。總之,該掛鉤是做爲通知之類使用的。雖然能夠用post-commit來觸發本地的持續集成系統,但大多數時候你想用的是post-receive這個鉤子。它運行在服務端而不是用戶的本地機器,它一樣在任何開發者推送代碼時運行。那裏更適合進行持續集成。

提交工做流的客戶端掛鉤腳本能夠在任何工做流中使用,他們常常被用來實施某些策略,但值得注意的是,這些腳本在clone期間不會被傳送。能夠在服務器端實施策略來拒毫不符合某些策略的推送,但這徹底取決於開發者在客戶端使用這些腳本的狀況。因此,這些腳本對開發者是有用的,由他們本身設置和維護,並且在任什麼時候候均可以覆蓋或修改這些腳本,後面講如何把這部分東西也集成到開發流中。

2.E-mail工做流掛鉤

有3個可用的客戶端掛鉤用於e-mail工做流。當運行 git am 命令時,會調用他們,所以,若是你沒有在工做流中用到此命令,能夠跳過本節。若是你經過e-mail接收由 git format-patch 產生的補丁,這些掛鉤也許對你有用。

首先運行的是 applypatch-msg 掛鉤,他接收一個參數:包含被建議提交信息的臨時文件名。若是該腳本非零退出,Git 放棄此補丁。可使用這個腳本確認提交信息是否被正確格式化,或讓腳本編輯信息以達到標準化。

下一個在 git am 運行期間調用是 pre-applypatch 掛鉤。該掛鉤不接收參數,在補丁被運用以後運行,所以,能夠被用來在提交前檢查快照。你能用此腳本運行測試,檢查工做樹。若是有些什麼遺漏,或測試沒經過,腳本會以非零退出,放棄這次git am的運行,補丁不會被提交。

最後在git am運行期間調用的是 post-applypatch 掛鉤。你能夠用他來通知一個小組或獲取的補丁的做者,但沒法阻止打補丁的過程。

3.其餘客戶端掛鉤

pre-rebase
pre-rebase 掛鉤在衍合前運行,腳本以非零退出能夠停止衍合的過程。你可使用這個掛鉤來禁止衍合已經推送的提交對象,pre-rebase 掛鉤樣本就是這麼作的。該樣本假定next是你定義的分支名,所以,你可能要修改樣本,把next改爲你定義過且穩定的分支名。

好比說,若是你想完全禁用rebase操做,你可使用下面的pre-rebase腳本:

#!/bin/sh

# 禁用全部rebase
echo "pre-rebase: Rebasing is dangerous. Don't do it."
exit 1

每次運行git rebase,你都會看到下面的信息:

pre-rebase: Rebasing is dangerous. Don't do it.
The pre-rebase hook refused to rebase.

內置的pre-rebase.sample腳本是一個更復雜的例子。它在什麼時候阻止rebase這方面更加智能。它會檢查你當前的分支是否已經合併到了下一個分支中去(也就是主分支)。若是是的話,rebase可能會遇到問題,腳本會放棄此次rebase。

post-checkout
git checkout命令調用,在完成工做區更新以後執行。該腳本由三個參數:以前HEAD指向的引用,新的HEAD指向的引用,一個用於標識這次檢出是不是分支檢出的值(0表示文件檢出,1表示分支檢出)。也能夠被git clone觸發調用,除非在克隆時使用參數--no-checkout。在由clone調用執行時,三個參數分別爲null, 1, 1。這個腳本能夠用於爲本身的項目設置合適的工做區,好比自動生成文檔、移動一些大型二進制文件等,也能夠用於檢查版本庫的有效性。

最後,在 merge 命令成功執行後,post-merge 掛鉤會被調用。他能夠用來在 Git 沒法跟蹤的工做樹中恢復數據,諸如權限數據。該掛鉤一樣可以驗證在 Git 控制以外的文件是否存在,所以,當工做樹改變時,你想這些文件能夠被複制。

服務器端 Hooks

除了客戶端掛鉤,做爲系統管理員,你還可使用兩個服務器端的掛鉤對項目實施各類類型的策略。這些掛鉤腳本能夠在提交對象推送到服務器前被調用,也能夠在推送到服務器後被調用。推送到服務器前調用的掛鉤能夠在任什麼時候候以非零退出,拒絕推送,返回錯誤消息給客戶端,還能夠如你所願設置足夠複雜的推送策略。

pre-receive
處理來自客戶端的推送(push)操做時最早執行的腳本就是 pre-receive 。它從標準輸入(stdin)獲取被推送引用的列表;若是它退出時的返回值不是0,全部推送內容都不會被接受。利用此掛鉤腳本能夠實現相似保證最新的索引中不包含非 fast-forward 類型的這類效果;抑或檢查執行推送操做的用戶擁有建立,刪除或者推送的權限或者他是否對將要修改的每個文件都有訪問權限。

#!/usr/bin/env python

import sys
import fileinput

# 讀取用戶試圖更新的全部引用
for line in fileinput.input():
    print "pre-receive: Trying to push ref: %s" % line

# 放棄推送
# sys.exit(1)

post-receive
post-receive 掛鉤在整個過程完結之後運行,能夠用來更新其餘系統服務或者通知用戶。它接受與 pre-receive 相同的標準輸入數據。應用實例包括給某郵件列表發信,通知實時整合數據的服務器,或者更新軟件項目的問題追蹤系統 —— 甚至能夠經過分析提交信息來決定某個問題是否應該被開啓,修改或者關閉。該腳本沒法組織推送進程,不過客戶端在它完成運行以前將保持鏈接狀態;因此在用它做一些消耗時間的操做以前請三思。

** update**
update 腳本和pre-receive腳本十分相似。不一樣之處在於它會爲推送者更新的每個分支運行一次。假如推送者同時向多個分支推送內容,pre-receive 只運行一次,相比之下 update 則會爲每個更新的分支運行一次。它不會從標準輸入讀取內容,而是接受三個參數:索引的名字(分支),推送前索引指向的內容的 SHA-1 值,以及用戶試圖推送內容的 SHA-1 值。若是 update 腳本以退出時返回非零值,只有相應的那一個索引會被拒絕;其他的依然會獲得更新。

husky是什麼?

husky 是一個 Git Hook 工具。husky 其實就是一個爲 git 客戶端增長 hook 的工具。將其安裝到所在倉庫的過程當中它會自動在.git/目錄下增長相應的鉤子實如今pre-commit階段就執行一系列流程保證每個 commit 的正確性。部分 cdcommit stage 執行的命令能夠挪動到本地執行,好比 lint 檢查、好比單元測試。固然,pre-commit 階段執行的命令固然要保證其速度不要太慢,每次 commit 都等好久也不是什麼好的體驗。

husky Github

husky安裝

npm install husky --save-dev
// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test",
      "pre-push": "npm test",
      "...": "..."
    }
  }
}
git commit -m 'Keep calm and commit'

保留現有的掛鉤。須要Node >= 10Git >= 2.13.0

從0.14升級

運行husky-upgrade以自動升級您的配置:

npx --no-install husky-upgrade

您也能夠手動執行。將現有的鉤子移至husky.hooks字段並使用原始Git鉤子名稱。另外,若是您使用的是GIT_PARAMS env 變量,請將其重命名爲HUSKY_GIT_PARAMS

{
  "scripts": {
-   "precommit": "npm test",
-   "commitmsg": "commitlint -E GIT_PARAMS"
  },
+ "husky": {
+   "hooks": {
+     "pre-commit": "npm test",
+     "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
+   }
+ }
}

從1.0.0開始,husky 可使用配置.huskyrc.huskyrc.json.huskyrc.jshusky.config.js文件。

// .huskyrc
{
  "hooks": {
    "pre-commit": "npm test"
  }
}

支持的掛鉤

Husky支持此處定義的全部Git鉤子。服務器端掛鉤(pre-receiveupdatepost-receive)不被支持。

訪問Git參數和標準輸入

Git掛鉤能夠經過命令行參數和stdin獲取參數。husky 使它們能夠經過HUSKY_GIT_PARAMSHUSKY_GIT_STDIN環境變量來訪問。

能夠簡單測試一下,你就能看到這些參數其實獲取到的就是你輸入的message信息

"commit-msg": "echo $HUSKY_GIT_PARAMS"

跳過全部掛鉤(從新定位)

在從新定位期間,您可能但願跳過全部掛鉤,可使用HUSKY_SKIP_HOOKS環境變量。

HUSKY_SKIP_HOOKS = 1 git rebase ...

禁用自動安裝

若是您不但願husky自動安裝Git掛鉤,只需設置HUSKY_SKIP_INSTALL環境變量便可。

HUSKY_SKIP_INSTALL=1 npm install

CI服務器

默認狀況下,Husky不會安裝在CI服務器上。

Monorepos

若是您有一個多程序包存儲庫,建議使用lerna之類的工具,而且僅將husky安裝在根目錄中package.json以充當真理的來源。

通常來講,應該避免在多箇中定義husky package.json,由於每一個軟件包都會覆蓋之前的husky安裝。

.
└── root
    ├── .git
    ├── package.json 🐶 # Add husky here
    └── packages
        ├── A
        │   └── package.json
        ├── B
        │   └── package.json
        └── C
            └── package.json
// root/package.json
{
  "private": true,
  "devDependencies": {
    "husky": "..."
  },
  "husky": {
    "hooks": {
      "pre-commit": "lerna run test"
    }
  }
}

節點版本管理器

若是您使用Windows,那麼husky只會使用系統上全局安裝的版本。

對於macOS和Linux用戶:

  • 若是您git在終端中運行命令,那麼husky將使用shell中定義的版本PATH。換句話說,若是您是nvm用戶,那麼husky將使用您設置的版本nvm
  • 若是您使用的是GUI客戶端和nvm,則它可能具備不一樣的PATH而不是未加載nvm,在這種狀況下,一般會選擇node安裝的最高版本nvm。您還能夠檢查~/.node_path以查看GUI使用哪一個版本,若是要使用其餘版本,也能夠進行編輯。

本地命令(〜/.huskyrc)

~/.huskyrc若是在運行鉤子腳本以前存在該文件,則Husky將提供源文件。您可使用它來例如加載節點版本管理器或shell在掛接前運行一些命令。

# ~/.huskyrc
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

多個命令

根據設計,就像scripts在中定義的同樣package.json,husky將鉤子腳本做爲單個命令運行。

"pre-commit": "cmd && cmd"

也就是說,若是您更喜歡使用數組,建議的方法是在中定義它們.huskyrc.js

const tasks = arr => arr.join(' && ')

module.exports = {
  'hooks': {
    'pre-commit': tasks([
      'cmd',
      'cmd'
    ])
  }
}

npm-run-all之類的工具也能夠提供幫助。

疑難排解

調試信息

HUSKY_DEBUG=1 在運行命令時能夠提供其餘信息。

HUSKY_DEBUG=1 npm install husky --save-dev
HUSKY_DEBUG=1 git commit ...
掛鉤沒有運行

檢查是否安裝了hooks(安裝完husky後,在項目中查看.git/hooks/目錄下是否存在多個文件,若是是空文件夾,就表明沒有安裝成功,須要卸載husky,再次從新安裝!!!)。確認.git/hooks/pre-commit存在而且具備hooks代碼。它應該以如下內容開頭:

#!/bin/sh
# husky...

若是沒有,您可能在package.json覆蓋沙啞的鉤子中定義了另外一個Git鉤子管理器。在安裝過程當中還要檢查輸出,您應該看到:

husky > Setting up git hooks
husky > Done
提交不被阻止

爲了阻止提交,pre-commit腳本必須以非零的退出代碼退出。若是您的提交未被阻止,請檢查腳本退出代碼。

提交很慢

Husky速度很快,並且提交的時間僅增長了十分之幾秒(~0.3s在低端PC上)。所以,這極可能與期間完成了多少操做有關pre-commit。您一般能夠經過在工具(babeleslint等)上使用緩存並使用lint-staged來改善此問題。

在新倉庫中測試husky

爲了找出問題,您還能夠建立一個新的倉庫:

mkdir foo && cd foo
git init && npm init -y
npm install husky --save-dev

# Add a failing pre-commit hook to your package.json:
# "pre-commit": "echo \"this should fail\" && exit 1"

# Make a commit
ENOENT錯誤'node_modules / husky / .git / hooks'

驗證您的Git版本是>=2.13.0

相關文章
相關標籤/搜索