如何使用GitHooks自动化开发和部署任务
介绍
版本控制已成为现代软件开发的核心要求。 它允许项目安全地跟踪更改并启用回退、完整性检查和协作等其他好处。
通过使用“挂钩”系统,git 允许开发人员和管理员通过指定 git 将根据不同事件和操作调用的脚本来扩展功能。
在本指南中,您将探索 git hooks 的概念,并演示如何实现可以帮助您在自己独特的环境中自动执行任务的代码。 在本指南中,您将使用 Ubuntu 20.04 服务器,但任何可以运行 git 的系统都应该以类似的方式工作。
先决条件
- 在开始之前,您必须在服务器上安装
git
。 如果您正在关注 Ubuntu 20.04,您可以查看我们关于 如何在 Ubuntu 20.04 上安装 git 的指南。 - 您应该熟悉如何在一般意义上使用 git。 如果您需要介绍,安装是其中一部分的系列,称为 Git 简介:安装、使用和分支 ,是一个很好的起点。
注意:如果您已经对 git 和 git hook 概念感到满意,并且想深入研究实际示例,您可以跳到“设置存储库”。
完成上述要求后,继续。
Git Hooks 的基本思想
Git 钩子是一个相当简单的概念,它是为了满足需求而实施的。 在共享项目上开发软件、维护风格指南标准或部署软件时,每次采取行动时,您经常需要执行重复性任务。
Git 挂钩是基于事件的。 当您运行某些 git 命令时,该软件将检查 git 存储库中的 hooks
目录,以查看是否有关联的脚本要运行。
一些脚本在操作发生之前运行,可用于确保代码符合标准、进行完整性检查或设置环境。 其他脚本在事件之后运行,以部署代码、重新建立正确的权限(git 无法很好地跟踪)等等。
使用这些功能,可以强制执行策略、确保一致性、控制您的环境,甚至处理部署任务。
定义挂钩类别
Scott Chacon 的书 Pro Git 试图将不同类型的钩子分为几类。 他将它们分类为:
- 客户端挂钩:在提交者的计算机上调用和执行的挂钩。 这些又分为几个单独的类别: 提交工作流钩子:提交钩子用于指示在提交时应该采取的行动。 它们用于运行健全性检查、预填充提交消息和验证消息详细信息。 您还可以使用它在提交时提供通知。 电子邮件工作流挂钩:此类挂钩包含使用电子邮件发送的补丁时采取的操作。 Linux 内核等项目使用电子邮件方法提交和审查补丁。 这些与提交挂钩类似,但可以由负责应用提交代码的维护人员使用。 其他:其他客户端钩子包括在合并、签出代码、变基、重写和清理存储库时执行的钩子。
- 服务器端挂钩:这些挂钩在用于接收推送的服务器上执行。 通常,这将是项目的主要 git 存储库。 再一次,Chacon 将这些分为几类: 预接收和后接收:这些在接收推送的服务器上执行,以执行检查项目一致性和推送后部署等操作。 更新:这类似于预接收,但在逐个分支的基础上运行以在每个分支被接受之前执行代码。
这些分类有助于大致了解您可以选择为其设置挂钩的事件。 但要真正了解这些项目的工作原理,最好进行试验并找出您正在尝试实施的解决方案。
按参数查看 Hooks
某些钩子也接受参数。 这意味着当 git 为 hook 调用脚本时,它会传入一些相关数据,然后脚本可以使用这些数据来完成任务。 完整的,可用的钩子是:
挂钩名称 | 调用者 | 描述 | 参数(编号和说明) |
---|---|---|---|
应用补丁消息 | git am
|
可以编辑提交消息文件,通常用于验证或主动将补丁消息格式化为项目标准。 非零退出状态中止提交。 | (1) 包含提议的提交消息的文件的名称 |
预应用补丁 | git am
|
这实际上称为 after 应用补丁,但 before 提交更改。 以非零状态退出将使更改处于未提交状态。 可用于在实际提交更改之前检查树的状态。 | (没有任何) |
应用后补丁 | git am
|
此挂钩在应用并提交补丁后运行。 因此,它不能中止进程,主要用于创建通知。 | (没有任何) |
预提交 | git commit
|
在获取建议的提交消息之前调用此钩子。 以非零值退出将中止提交。 它用于检查提交本身(而不是消息)。 | (没有任何) |
准备提交消息 | git commit
|
在收到默认提交消息后调用,就在启动提交消息编辑器之前。 非零退出会中止提交。 这用于以无法抑制的方式编辑消息。 | (1 到 3)带有提交消息的文件的名称,提交消息的来源(message 、template 、merge 、squash 或commit )和提交 SHA-1(在对现有提交进行操作时)。
|
提交消息 | git commit
|
可用于在编辑消息后调整消息,以确保符合标准或根据任何标准拒绝。 如果它以非零值退出,它可以中止提交。 | (1) 保存建议消息的文件。 |
提交后 | git commit
|
在进行实际提交后调用。 因此,它不能中断提交。 它主要用于允许通知。 | (没有任何) |
预变基 | git rebase
|
在重新设置分支时调用。 主要用于在不需要时停止变基。 | (1 或 2)分叉的上游,分支正在变基(变基时不设置当前) |
结帐后 | git checkout 和 git clone
|
在更新工作树或 git clone 之后调用检出时运行。 主要用于验证条件,显示差异,必要时配置环境。
|
(3)前一个HEAD的ref,新HEAD的ref,flag表示是分支检出(1)还是文件检出(0) |
合并后 | git merge 或 git pull
|
合并后调用。 因此,它不能中止合并。 可用于保存或应用 git 无法处理的权限或其他类型的数据。 | (1) 指示合并是否为壁球的标志。 |
预推 | git push
|
在推送到远程之前调用。 除了参数之外,额外的信息,用空格隔开,通过标准输入以“ ”。 解析输入可以获得可用于检查的其他信息。 例如,如果本地 sha1 长度为 40 个零,则推送为删除,如果远程 sha1 为 40 个零,则为新分支。 这可用于将推送的 ref 与当前存在的内容进行许多比较。 非零退出状态中止推送。 | (2)目标远程的名称,目标远程的位置 |
预收 | 远程仓库上的 git-receive-pack
|
这在更新推送的参考之前在远程仓库上调用。 非零状态将中止该过程。 虽然它不接收参数,但它通过标准输入以“ ” 对于每个参考。 | (没有任何) |
更新 | 远程仓库上的 git-receive-pack
|
对于每个推送的 ref,这在远程 repo 上运行一次,而不是每次推送一次。 非零状态将中止该过程。 例如,这可用于确保所有提交都只是快进。 | (3) 正在更新的ref的名称,旧的对象名,新的对象名 |
接收后 | 远程仓库上的 git-receive-pack
|
在所有参考更新后推送时,这将在远程运行。 它不带参数,而是通过stdin以“ ”。 因为它是在更新之后调用的,所以它不能中止进程。 | (没有任何) |
更新后 | 远程仓库上的 git-receive-pack
|
这仅在所有 refs 被推送后运行一次。 它在这方面类似于 post-receive 钩子,但不接收旧值或新值。 它主要用于实现推送 refs 的通知。 | (?) 每个推送的 refs 的参数包含其名称 |
预自动 gc | git gc --auto
|
用于在自动清理 repos 之前进行一些检查。 | (没有任何) |
重写后 | git commit --amend 、git-rebase
|
当 git 命令正在重写已经提交的数据时调用它。 除了参数之外,它在标准输入中以“ ”。 | (1) 调用它的命令的名称(amend 或 rebase )
|
关于使用 Git Hooks 的环境变量的旁白
在开始编写脚本之前,您需要了解一下 git 在调用钩子时设置的环境变量。 为了让您的脚本正常运行,您最终需要取消设置 git 在调用 post-commit
挂钩时设置的环境变量。
如果您希望编写以可靠方式运行的 git 钩子,这是一个非常重要的内化点。 Git 根据调用的钩子设置不同的环境变量。 这意味着 git 从中提取信息的环境将根据钩子而有所不同。
第一个问题是,如果您不知道自动设置了哪些变量,它会使您的脚本环境变得非常不可预测。 第二个问题是设置的变量在 git 自己的文档中几乎完全没有。
幸运的是,Mark Longair 开发了 一种方法来测试运行这些钩子时 git 设置的每个变量 。 它涉及将以下内容放入各种 git hook 脚本中:
#!/bin/bash echo Running $BASH_SOURCE set | egrep GIT echo PWD is $PWD
他网站上的信息是从 2011 年开始使用 git 版本 1.7.1,所以有一些变化。 本指南使用 Ubuntu 20.04 和 git 2.25.1。
在这个版本的 git 上的测试结果如下(包括运行每个钩子时 git 看到的工作目录)。 测试的本地工作目录是 /home/sammy/test_hooks
,裸远程(如果需要)是 /home/sammy/origin/test_hooks.git
:
- Hooks:
applypatch-msg
,pre-applypatch
,post-applypatch
- Hooks:
pre-commit
,prepare-commit-msg
,commit-msg
,post-commit
- Hooks:
pre-rebase
- Hooks:
post-checkout
- 挂钩 :
post-merge
环境变量:GITHEAD_4b407c... GIT_DIR=.git GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu GIT_PREFIX= GIT_REFLOG_ACTION='pull other master' 工作目录:/home/sammy/test_hooks - Hooks:
pre-push
- 挂钩 :
pre-receive
,update
,post-receive
,post-update
环境变量:GIT_DIR=。 工作目录:/home/sammy/origin/test_hooks.git - Hooks:
pre-auto-gc
- Hooks:
post-rewrite
这些变量对 git 如何看待其环境有影响。 您将使用上述有关变量的信息来确保您的脚本正确考虑其环境。
现在您已经掌握了所有这些一般信息,您可以演示如何在几个场景中实现这些信息。
设置存储库
首先,您将在主目录中创建一个新的空存储库。 你可以称之为proj
。
mkdir ~/proj cd ~/proj git init
OutputInitialized empty Git repository in /home/sammy/proj/.git/
对于本指南的其余部分,根据需要将 sammy 替换为您的用户名。
现在,您位于 git 控制目录的空工作目录中。 在您执行任何其他操作之前,请跳转到存储在此目录中名为 .git
的隐藏文件中的存储库:
cd .git ls -F
Outputbranches/ config description HEAD hooks/ info/ objects/ refs/
您将看到许多文件和目录。 您感兴趣的是 hooks
目录:
cd hooks ls -l
Outputtotal 40 -rwxrwxr-x 1 sammy sammy 452 Aug 8 16:50 applypatch-msg.sample -rwxrwxr-x 1 sammy sammy 896 Aug 8 16:50 commit-msg.sample -rwxrwxr-x 1 sammy sammy 189 Aug 8 16:50 post-update.sample -rwxrwxr-x 1 sammy sammy 398 Aug 8 16:50 pre-applypatch.sample -rwxrwxr-x 1 sammy sammy 1642 Aug 8 16:50 pre-commit.sample -rwxrwxr-x 1 sammy sammy 1239 Aug 8 16:50 prepare-commit-msg.sample -rwxrwxr-x 1 sammy sammy 1352 Aug 8 16:50 pre-push.sample -rwxrwxr-x 1 sammy sammy 4898 Aug 8 16:50 pre-rebase.sample -rwxrwxr-x 1 sammy sammy 3611 Aug 8 16:50 update.sample
你可以在这里看到一些东西。 首先,您可以看到这些文件中的每一个都被标记为可执行文件。 由于这些脚本只是按名称调用,因此它们必须是可执行的,并且它们的第一行必须是 shebang 幻数 引用才能调用正确的脚本解释器。 最常见的是,这些是脚本语言,如 bash、perl、python 等。
您可能会注意到的第二件事是所有文件都以 .sample
结尾。 这是因为 git 在尝试查找要执行的挂钩文件时只是查看文件名。 偏离 git 正在寻找的脚本名称基本上会禁用该脚本。 为了启用此目录中的任何脚本,您必须删除 .sample
后缀。
第一个示例:使用 Post-Commit Hook 部署到本地 Web 服务器
您的第一个示例将使用 post-commit
挂钩向您展示如何在提交时部署到本地 Web 服务器。 这不是您将用于生产环境的钩子,但它让我们展示了一些重要的、几乎没有记录的项目,您在使用钩子时应该了解这些内容。
首先,您将安装 Apache Web 服务器来演示:
sudo apt-get update sudo apt-get install apache2
为了让您的脚本修改 /var/www/html
的 Web 根目录(这是 Ubuntu 20.04 上的文档根目录。 根据需要修改),你需要有写权限。 首先,让您的普通用户拥有该目录的所有权。 您可以通过键入以下内容来执行此操作:
sudo chown -R `whoami`:`id -gn` /var/www/html
现在,在您的项目目录中,创建一个 index.html
文件:
cd ~/proj nano index.html
在里面,你可以添加一点 HTML 来演示这个想法。 它不必很复杂:
索引.html
<h1>Here is a title!</h1> <p>Please deploy me!</p>
添加新文件以告诉 git 跟踪文件:
git add .
现在, 在 提交之前,您将为存储库设置 post-commit
挂钩。 在项目的 .git/hooks
目录中创建此文件:
nano .git/hooks/post-commit
由于 git 挂钩是标准脚本,因此您需要以 shebang 开头来告诉 git 使用 bash:
#!/bin/bash unset GIT_INDEX_FILE git --work-tree=/var/www/html --git-dir=$HOME/proj/.git checkout -f
在下一行中,您需要仔细查看每次调用 post-commit
挂钩时设置的环境变量。 特别是,将 GIT_INDEX_FILE
设置为 .git/index
。
此路径与工作目录相关,在本例中为 /var/www/html
。 由于此位置不存在 git 索引,因此如果您保持原样,脚本将失败。 为避免这种情况,您可以手动 unset 变量。
之后,您只需在提交后使用 git 本身将最新版本的存储库解压到您的 web 目录中。 您将需要强制执行此事务以确保每次都成功。
完成这些更改后,保存并关闭文件。
因为这是一个常规的脚本文件,所以您需要使其可执行:
chmod +x .git/hooks/post-commit
现在,您终于准备好提交您在 git 存储库中所做的更改。 确保您回到正确的目录,然后提交更改:
cd ~/proj git commit -m "here we go..."
现在,如果您在浏览器中访问服务器的域名或 IP 地址,您应该会看到您创建的 index.html
文件:
http://server_domain_or_IP
如您所见,您最近的更改已在提交时自动推送到 Web 服务器的文档根目录。 您可以进行一些额外的更改以显示它适用于每次提交:
echo "<p>Here is a change.</p>" >> index.html git add . git commit -m "First change"
刷新浏览器时,您应该会立即看到您应用的新更改:
如您所见,这种类型的设置可以使本地测试更改变得更容易。 但是,您几乎永远不想在生产环境中发布提交。 在您测试了代码并确定它已准备好之后,推送会更安全。
使用 Git Hooks 部署到单独的生产服务器
在下一个示例中,您将演示一种更新生产服务器的更好方法。 您可以使用推送部署模型来执行此操作,以便在推送到裸 git 存储库时更新您的 Web 服务器。 您可以使用您设置的同一台服务器作为您的开发机器。
在您的生产机器上,您将设置另一个 Web 服务器、一个将更改推送到的裸 git 存储库,以及一个在收到推送时执行的 git 挂钩。
以具有 sudo 权限的普通用户身份完成以下步骤。
设置生产服务器接收后挂钩
在生产服务器上,首先安装 Web 服务器:
sudo apt-get update sudo apt-get install apache2
同样,您应该将文档根目录的所有权授予您正在操作的用户:
sudo chown -R `whoami`:`id -gn` /var/www/html
您还需要在这台机器上安装 git:
sudo apt-get install git
现在,您可以在用户的主目录中创建一个目录来保存存储库。 然后,您可以进入该目录并初始化一个裸存储库。 裸存储库没有工作目录,更适合您不会直接使用的服务器:
mkdir ~/proj cd ~/proj git init --bare
由于这是一个裸存储库,因此没有工作目录,并且通常位于 .git
中的所有文件现在都在主目录中。
接下来,您需要创建另一个 git 挂钩。 这一次,您对 post-receive
挂钩感兴趣,该挂钩在接收 git push
的服务器上运行。 在编辑器中打开此文件:
nano hooks/post-receive
同样,您需要首先确定您正在编写的脚本类型。 之后,您可以键入在 post-commit
文件中使用的相同检查命令,修改为使用本机上的路径:
#!/bin/bash while read oldrev newrev ref do if [[ $ref =~ .*/master$ ]]; then echo "Master ref received. Deploying master branch to production..." git --work-tree=/var/www/html --git-dir=$HOME/proj checkout -f else echo "Ref $ref successfully received. Doing nothing: only the master branch may be deployed on this server." fi done
由于这是一个裸存储库,因此 --git-dir
应该指向该存储库的顶级目录。
但是,您需要向此脚本添加一些额外的逻辑。 如果您不小心将 test-feature
分支推送到此服务器,您不希望部署该分支。 您要确保只部署 master
分支。
首先,您需要读取标准输入。 对于每个被推送的 ref,三条信息(旧 rev、新 rev、ref)将作为标准输入提供给脚本,以空格分隔。 您可以使用 while
循环来围绕 git
命令读取此内容。
所以现在,您将根据推送的内容设置三个变量。 对于主分支推送,ref
对象将包含类似于 refs/heads/master
的内容。 您可以使用 if
构造检查服务器接收的 ref 是否具有这种格式。
最后,添加一些描述检测到的情况以及采取了哪些措施的文本。 您应该添加一个 else
块以在成功接收到非主分支时通知用户,即使该操作不会触发部署。
完成后,保存并关闭文件。 但请记住,您必须使脚本可执行才能使挂钩起作用:
chmod +x hooks/post-receive
现在,您可以在客户端上设置对此远程服务器的访问。
在客户端计算机上配置远程服务器
回到你的客户端(开发)机器上,回到你项目的工作目录:
cd ~/proj
在里面,将远程服务器添加为名为 production
的远程服务器。 您键入的命令应如下所示:
git remote add production sammy@remote_server_domain_or_IP:proj
现在将您当前的主分支推送到您的生产服务器:
git push production master
如果您没有配置 SSH 密钥,您可能需要输入生产服务器用户的密码。 您应该会看到如下所示的内容:
Output]Counting objects: 8, done. Delta compression using up to 2 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (4/4), 473 bytes | 0 bytes/s, done. Total 4 (delta 0), reused 0 (delta 0) remote: Master ref received. Deploying master branch... To sammy@107.170.14.32:proj 009183f..f1b9027 master -> master
如您所见,来自 post-receive
钩子的文本在命令的输出中。 如果您在 Web 浏览器中访问生产服务器的域名或 IP 地址,您应该会看到项目的当前版本:
看起来钩子在收到信息后已成功将您的代码推送到生产环境。
现在,是时候测试一些新代码了。 回到开发机器上,您将创建一个新分支来保存您的更改。 创建一个名为 test_feature
的新分支并通过键入以下命令检查新分支:
git checkout -b test_feature
您现在在 test_feature
分支中工作。 尝试做出您 可能 想要转移到生产环境的更改。 你将把它提交到这个分支:
echo "<h2>New Feature Here</h2>" >> index.html git add . git commit -m "Trying out new feature"
此时,如果您转到开发机器的 IP 地址或域名,您应该会看到显示的更改:
这是因为您的开发机器仍在每次提交时重新部署。 此工作流程非常适合在将更改转移到生产之前对其进行测试。
您可以将 test_feature
分支推送到远程生产服务器:
git push production test_feature
您应该在输出中看到来自 post-receive
挂钩的另一条消息:
OutputCounting objects: 5, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 301 bytes | 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0) remote: Ref refs/heads/test_feature successfully received. Doing nothing: only the master branch may be deployed on this server To sammy@107.170.14.32:proj 83e9dc4..5617b50 test_feature -> test_feature
如果您再次在浏览器中检查生产服务器,您应该会看到没有任何变化。 这是您所期望的,因为您推送的更改不在 master 分支中。
现在您已经在开发机器上测试了您的更改,您确定要将此功能合并到您的主分支中。 您可以签出您的 master
分支并合并到您的开发机器上的 test_feature
分支中:
git checkout master git merge test_feature
现在,您已将新功能合并到主分支中。 推送到生产服务器将部署您的更改:
git push production master
如果您检查生产服务器的域名或 IP 地址,您将看到您的更改:
使用此工作流程,您可以拥有一台可以立即显示任何已提交更改的开发机器。 每当您推送主分支时,生产机器都会更新。
结论
如果您已经遵循了这一点,您应该能够看到 git hooks 可以帮助自动化您的某些任务的不同方式。 它们可以帮助您部署代码,或者通过拒绝不符合要求的更改或提交消息来帮助您维护质量标准。
虽然 git hooks 的实用性很难争论,但实际的实现可能相当难以掌握并且令人沮丧地排除故障。 练习实现各种配置,尝试解析参数和标准输入,以及跟踪 git 如何构建钩子的环境,这将大大有助于教你如何编写有效的钩子。 从长远来看,时间投资通常是值得的,因为它可以轻松地为您和您的团队在项目生命周期中节省大量体力劳动。
要开始使用 git 为项目做贡献,请查看 如何为开源做贡献:Git 入门 。 或者,如果您对更多使用 git 的方法感兴趣,请尝试 如何使用 Git:参考指南 。