GitHook 工具 —— husky介紹及使用


名稱

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

描述

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

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

Git Hooks 能做什么

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

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

Git Hooks 是如何工作的

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

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

Git 默認會放置一些腳本樣本在這個目錄中,除了可以作為掛鈎使用,這些樣本本身是可以獨立使用的。所有的樣本都是shell腳本,其中一些還包含了Perl的腳本。不過,任何正確命名的可執行腳本都可以正常使用 ,也可以用Ruby或Python,或其他腳本語言。

上圖是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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM