可选的Git钩子表现为非可选

4

我正尝试在我的工作流中使用这个代码片段作为post-mergepost-checkout git钩子。

#!/usr/bin/env bash
# MIT © Sindre Sorhus - sindresorhus.com

# git hook to run a command after `git pull` if a specified file was changed
# Run `chmod +x post-merge` to make it executable then put it into `.git/hooks/`.

changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"

check_run() {
    echo "$changed_files" | grep --quiet "$1" && eval "$2"
}

# Example usage
# In this example it's used to run `npm install` if package.json changed
check_run package.json "npm install"

该代码声称只在package.json文件被更改时运行npm安装命令。

然而,在我尝试过的所有机器上,无论是否更改了package.json文件,都会运行npm安装命令。

为了测试这个问题,我创建了一个新分支,并在当前提交处检出它,从而触发了post-checkout git钩子。我不希望运行npm install命令,因为package.json没有更改。

视觉证明(请注意npm警告文本):

enter image description here

3个回答

2

ORIG_HEAD应该被替换为HEAD@{1},如此问题中所述。ORIG_HEAD是一个较旧、不太可靠的方式,用来获取HEAD的先前状态。在我的情况下,它没有被设置。


1
如果启用了reflogs,使用“HEAD@(1)”才能起作用。(当然,它们很可能已经启用了。) - torek

1

TL;DR

使用不同的post-checkout hook,使用$1代替ORIG_HEAD。(或者检查参数数量来决定你是作为post-checkout还是post-merge hook被调用,以达到相同的效果。或者,如果你知道reflogs总是启用的,使用HEAD@{1}来获取HEAD的上一个值。)

讨论

在post-merge hook中使用ORIG_HEAD是有意义的,因为git mergeORIG_HEAD设置为合并之前当前的提交。(如果合并是真正的合并,而不是快进合并,则由MERGE_HEADHEAD^1所标识的提交必须是相同的。但是,如果合并是快进合并,则只有MERGE_HEAD和reflog才能找到存储在合并之前HEAD中的先前提交哈希。)

在一个post-checkout钩子中使用ORIG_HEAD是明显错误的,因为git checkout不会设置ORIG_HEAD。这意味着如果ORIG_HEAD存在,它实际上指向某个随机提交。(当然,它实际上解析为最后一次更新它的任何命令留下的任何提交:git mergegit rebase或任何其他写入ORIG_HEAD的命令。但这里的重点是它与检出之前的提交没有任何关系。)一个post-checkout hook:

给定三个参数:先前HEAD的ref,新HEAD的ref(可能已更改,也可能未更改)以及指示检出是分支检出(更改分支,flag=1)还是文件检出(从索引中检索文件,flag=0)的标志。此钩子无法影响git checkout的结果。

那最后一句话不是很准确。虽然 post-checkout 钩子无法阻止 checkout 已经更新了索引和工作树,但它可以覆盖各种工作树或索引内容,并且如果它产生失败的退出状态,则会导致 git checkout 本身也产生失败的退出状态。

这意味着在 post-checkout 钩子中需要采取不同的操作:使用第一个参数 $1 来获取前一个 HEAD 的哈希 ID。请注意,在特殊情况下1,post-checkout 钩子会在初始的 git clone 上运行,因此 $1 可能是 null-ref。(现在我很好奇当你使用 git checkout --orphan 然后不创建新分支时,它是什么。看起来 $1 在这里也将是 null-ref。)


1唯一让git clone在运行后钩子时生效的方法是让git clone安装后钩子。这通常是不可能的,但可以通过将Git指向具有实际钩子而不仅仅是示例钩子的自己的模板目录来实现。


关于代码的一个有些不相关的问题。我现在已经在你上面看到的内容后面添加了另一行,它是 check_run .gitmodules "git submodule udpate",但是出于某种原因,如果 package.json 没有改变,它永远不会运行新的那一行。你有什么想法吗? - jth41
这似乎很奇怪。为了调试脚本,可以使用 set -x(或在 #! 行的标志中添加 -x——这可能在此处不可行,取决于内核和 #! 解释),然后您可以观察它运行每个命令。我在这里注意到 udpate 有一个拼写错误(交换了 d 和 p),但当然这可能只是你在评论中的错误,而不是脚本本身的错误。 - torek
我将其作为一个新问题写了出来,包括您建议的输出方法:https://dev59.com/96Hia4cB1Zd3GeqPWJkX - jth41

1

Torek提到:

This hook cannot affect the outcome of git checkout.

That last sentence is not quite right.

Although the post-checkout hook cannot stop checkout from having updated the index and work-tree, it can overwrite various work-tree or index contents, and if it produces a failure exit status, it causes git checkout itself to also produce a failure exit status.

现在已经(2020年第四季度,三年后)正式在Git 2.29中记录下来:

请参见提交3100fd5(2020年8月27日),由Junio C Hamano (gitster)提交。
(由Junio C Hamano -- gitster --合并于提交2f1757e,2020年9月3日)

doc: 澄清 post-checkout 钩子的退出状态的用途

因为此钩子在主要检出操作完成后运行,所以它不能影响当前分支是哪个分支,更新工作树中的哪些路径等,这被描述为“不能影响 'checkout' 的结果”。

但是,钩子的 exit 状态用作 'checkout' 命令的 exit 状态,并且由任何生成 'checkout' 的人观察到,这在文档中缺失。
修复此问题。

githooks 现在包含在其手册页中:

这个钩子不能影响 git switchgit checkout 的结果,除非该钩子的退出状态成为这两个命令的退出状态


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接