【问题标题】:post-receive hook with deploy commands not working properly带有部署命令的接收后挂钩无法正常工作
【发布时间】:2015-12-14 02:52:37
【问题描述】:

我在遥控器上有一个 git,并添加了一个接收后挂钩。 post-receive 挂钩应执行以下操作。

如果[composer] 出现在提交消息中,请进行作曲家更新
如果[deploy] 出现在提交消息中,则检查已推送的分支(例如feature/testmaster 等)
如果[migrate] 出现在提交消息中,请执行php artisan migrate

不幸的是,这不起作用。钩子被调用,但动作是“错误的”。

这是脚本

#!/bin/sh
MESSAGE=$(git log -1 HEAD --pretty=format:%s)

if [[ "$MESSAGE" == *\[composer\]* ]]; then
    composer --working-dir=/var/www/feedev/ update
fi

if [[ "$MESSAGE" == *\[deploy\]* ]]; then
    while read oldrev newrev ref
do
    branch=`echo $ref | cut -d/ -f3`
    git --work-tree=/var/www/feeddev/ --git-dir=/home/feeddev/staging.git checkout -f $branch
done
else
    git --work-tree=/var/www/feedev/ --git-dir=/home/feeddev/staging.git checkout -f
fi

if [[ "$MESSAGE" == *\[migrate\]* ]]; then
    php /var/www/feedev/artisan migrate
fi

这不起作用,因为出于测试目的,我添加了这些行

touch /home/ezidev/ezidev.git/$MESSAGE.lock

在读取消息变量后,如果第二种情况为假,则添加此

touch /home/ezidev/ezidev.git/nodeploy.lock

所以现在我正在提交,并且有提交消息 [deploy],但是没有签出新分支,并且还生成了两个新文件

touch /home/ezidev/ezidev.git/$MESSAGE.lock 生成/home/ezidev/ezidev.git/static.lock

touch /home/ezidev/ezidev.git/nodeploy.lock 生成/home/ezidev/ezidev.git/nodeploy.lock

为什么 $MESSAGE = static.lock?我没有把它写到我的提交消息中。可能是什么问题,我该如何解决?

【问题讨论】:

  • 你的 git 远程用户有正确的权限吗?
  • 我需要什么?我想我得到了正确的,因为它正在创建文件,或者你的意思是什么。还有吗?
  • 您是否也需要git log 调用中的--work-tree 和/或--git-dir 参数?
  • 我查看了文档:git-scm.com/docs/git-log,但不幸的是,没有工作树和/或 git-dir 的日志参数
  • @Musterknabe: git log 不检查工作树,因此它不需要--work-tree。它确实检查了 git 目录,并且确实采用了 --git-dir--git-dir--work-tree 标志由 git 前端命令处理,而不是后端 logcheckout,所以所有命令接受它们)但你不需要在这里设置任何东西。

标签: git bash version-control hook


【解决方案1】:

请记住,在 git 收集新对象并更新一些引用(例如 refs/heads/masterrefs/heads/bra/nch 和/或 refs/tags/v1.3)之后,会运行一个 post-receive 挂钩。 (“引用”是分支、标签、特殊存储引用等的通用名称。在“接收”操作中通常更新的两个是分支和标签,带有标签更新通常只是创建一个新标签。不过,其他更新是可能的,这取决于您在预接收和更新挂钩中允许的内容。此外,如果您使用 git 的“注释”,您将看到以refs/notes/开头的名称。)

考虑到这一点,让我们看看脚本中的第一个命令:

MESSAGE=$(git log -1 HEAD --pretty=format:%s)

这使用HEADHEAD 指的是什么提交?

这不仅仅是一个修辞问题。 HEAD 引用通常是对分支的间接引用:它指定存储库“打开”的分支,就当前签出而言。运行 git checkout $branch 会更改 HEAD 的内容。即使在裸存储库中也是如此:HEAD 指的是某个分支,最初是 master,但可以更改,并且在您的情况下可能会更改,因为脚本中有一些 git checkout 命令。

我无法知道您的脚本运行时HEAD 中的内容(一般来说,如果脚本运行,它可能已经更改了HEAD,因此您可能很难甚至不可能找出)。但这将决定检查哪个提交 git logHEAD 可能包含一个分支名称,分支名称将指示一个特定的提交,这将是 git log 使用的(单个)提交。

现在让我们再次回到 post-receive 钩子实际得到的东西。它在接收到一些对象(甚至可能是数千个提交)并更新一些引用(甚至可能是几十个引用)之后运行。然后,您正在查看 one 分支上的 one 提交,这可能是也可能不是许多已更新的分支之一,并使用它来决定如何处理 所有可能是许多分支上的许多提交的更新。

这似乎不太可能是正确的。

我无法真正为您编写钩子,但让我们再看一下您已经拥有的部分内容,其中包含一些正确的代码(以及一些最好的代码):

while read oldrev newrev ref
do
    branch=`echo $ref | cut -d/ -f3`
    git --work-tree=/var/www/feeddev/ --git-dir=/home/feeddev/staging.git checkout -f $branch
done

post-receive 钩子被告知更新了哪些引用,以及以何种方式更新,作为标准输入上的一组行。这个while 循环读取这些行。这意味着while 部分是正确的。

第一个命令 within 至少在某种程度上被破坏了。从标准输入读取一行后,$ref 将类似于refs/heads/masterrefs/heads/bra/nchrefs/tags/v1.3。 echo-and-cut 序列只取每个单词的第三个单词:masterbrav1.3。第一个是可以的:它是全名refs/heads/master的缩写,即分支master。第二个不行:它是分支bra/nch 的缩写形式的一部分,但它只是它的一部分,它不能很好地工作。第三个可能也不行:它是名称refs/tags/v1.3 的缩写形式,即标记v1.3,但它是一个标记,而不是一个分支。似乎有一些不同的东西适合这个。

接下来,脚本会在读取 $oldrev$newrev 后忽略它们。这适用于大多数(但不是全部)接收后操作:如果 $ref 已经存在,$oldrev 是它过去指向的 SHA-1,$newrev 是它现在指向的 SHA-1更新后。例如,如果您刚刚将一些新的提交推送到分支 feat/ure 以便以快进方式更新分支,则 $refrefs/heads/feat/ure 并且使用 git rev-list $oldrev..$newrev 将获得 SHA-每个新提交 1 秒。 (如果是强制推送——例如,客户端上的git push -fgit push +sha1:refs/heads/feat/ure——一些提交可能会被“带走”,你可以通过git rev-list $newrev..$oldrev 找到它们。)

但还有两种情况需要考虑:首先,$ref 刚刚创建。这是标签的正常情况,因为标签通常不应该被更改,只能创建。在这种情况下,$oldrev 将全为零(四十个0 字符)。在一般情况下,不可能找到哪些提交(如果有)仅在此引用上(尽管可以使用一些技巧,例如,添加一个 pre em>-receive 钩子来禁止无法弄清楚的更新,这样你就只剩下可处理的特殊情况了)。最后一种情况是$ref 正在被删除,例如,有人运行git push origin :bra/nch 从服务器上的裸存储库中删除refs/heads/bra/nch。在这种情况下,$oldrev 将告诉您 SHA-1 refs/heads/bra/nch 是什么,$newrev 将是特殊的 40 0s "null SHA-1"。您将无法签出该分支,因为它刚刚被删除。

最后,git checkout -f 指定了一个特定的工作树和 git 目录(后者覆盖了在接收后挂钩中设置的$GIT_DIR)。这些可能是正确的路径,但$branch 可能不是分支名称:在我们的示例中,它是master,然后是bra,然后是v1.3,依次是通过@987654382 的三个行程中的每一个@循环。


简而言之(我知道,“为时已晚”:-))这个特殊的接收后挂钩存在严重缺陷。你需要弄清楚你真正想要处理的情况,然后编写一个新的情况,从运行while 循环开始:

while read oldrev newrev ref; do
    ...
done

在循环内,检查所有三个变量。根据 40-0s null-SHA1 检查 $oldrev$newrev 以确定操作是创建、删除还是更新。然后检查$ref 的第一个组件以找到引用的名称空间;如果是分支,以refs/heads/开头,然后可以去掉refs/heads/部分得到分支名称;如果是其他原因,您或许可以只使用continue 循环来忽略更改。

如果您要使用cut 剥离refs/heads/,请务必使用-f3- 而不仅仅是-f3。 (我更喜欢使用 shell 的内置功能来剥离字符串,${ref#refs/heads/} 我自己,但 echo ${ref} | cut -d/ -f3- 确实有效。)

然后,如果是分支更新(不是创建,不是删除,只是更新),您可以使用 git rev-list $oldrev..$newrev 查找添加到该分支的提交。在每一个上使用git log(或者为了提高效率,git log $oldrev..$newrevgit log 将为您运行git rev-list)来检查他们的提交主题行和/或提交消息正文中的关键字。根据每次提交中给出的(可能是多个)关键字,在提交和/或分支上采取适当的行动,无论“适当”是什么。

【讨论】:

  • 您的评论对我帮助很大。使用git log -1 HEAD --pretty=format:%s 时,我可以看到它获得了当前所在分支的最后提交消息。所以我推送开发,但收到的消息来自master。这是朝着正确方向迈出的第一步。如果我看到更多东西,我会发消息给你
  • 您仍然缺少一个关键点:单个git push 可以推送到两个分支、三个分支、四个或五个或更多分支。 post-receive 挂钩获取所有这些更新,因此您必须检查所有这些更新并将您的操作基于可能的许多分支、标签或其他更新。
  • 我理解这种担忧,但是,目前我们一次推送多个分支的用例不存在。如果我们想要在线的特定功能,我们推送该分支。如果我们想一次查看多个功能,我们有一个临时分支。所以另一个分支被单推。我们正在使用 SourceTree,因此“不应该”同时推送多个分支
  • 您可以通过包含一个 pre-receive 挂钩来保证不会发生这种情况,如果其中有多个分支(或您希望要求的任何其他条件),该挂钩会拒绝传入的更新。然而,这通常是错误的方法:通常正确的方法是简单地处理它们。循环,读取所有更新;如果分支 A 已更新,请检查 A 上的新提交;然后如果分支 B 被更新,检查那些提交;等等。
猜你喜欢
  • 2012-08-08
  • 1970-01-01
  • 2015-12-18
  • 1970-01-01
  • 2012-05-18
  • 2018-10-13
  • 2020-05-07
  • 2021-06-16
  • 2013-07-12
相关资源
最近更新 更多