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

相关文章
相关标签/搜索