Git 挂钩无提示地失败

Git Hook Fails Silently

我有一个 post-checkout 和 post-merge githook 与这些内容:

#!/bin/bash
# MIT © Sindre Sorhus - sindresorhus.com
set -eux  

changed_files="$(git diff-tree -r --name-only --no-commit-id HEAD@{1} HEAD)"

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

echo ''
echo 'running git submodule update --init --recursive if .gitmodules has changed'
check_run .gitmodules "git submodule update --init --recursive"

echo ''
echo 'running npm install if package.json has changed'
check_run package.json "npm prune && npm install"

echo ''
echo 'running npm build:localhost'
npm run build:localhost 

奇怪的是,如果 .gitmodules 没有变化,脚本会结束而不是继续检查 package.json。 (它甚至不执行第 12 行之后的回显行)

删除 check_run 调用并只输入直接命令似乎工作正常。

删除 check_run .gitmodules "git submodule update --init --recursive" 也有效。然而,下一行显示了相同的行为:check_run package.json "npm prune && npm install" if package.json has not been changed

我是否遗漏了什么导致 check_run 在未找到第一个文件更改时结束脚本?

视觉证明:

set -e是这里的问题:-e表示"exit if something fails",其中"fails"定义为"exits nonzero"。

我们在输出中看到,如最后几行:

+ check_run .gitmodules 'git submodule update --init --recursive'
+ echo ''
+ grep --quiet .gitmodules

(然后没有别的)。脚本在 grep --quiet.

后退出

这里是 check_run 的实际定义:

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

这解析为 <em>left</em> && <em>right</em> where leftecho ... | grep ...eval "".

我们看到左边部分 运行 而右边部分没有。这里是我们需要了解一些关于 shells 的地方:即使设置了 -e,它们也不会 立即 在出现故障时退出,as只要那东西是测试的一部分.1 所以这不是直接的问题。

但还是有问题,因为<em>left</em> && <em>right</em>有,因为它退出状态,最后一件事的退出状态运行s。它 运行 的最后一件事是 left 管道,即 echo ... | grep ...。管道的退出状态是其最后一个组件的退出状态,2grep。如果 grep 找到字符串(并且使用 --quiet,也会抑制其输出),则 grep 退出为零,否则为 1,一般错误为 2,因此退出状态为 1.

因此<em>left</em> && <em>right</em>的退出状态也是1.

因此,由于-e生效,shell退出!

解决方法是避免 -e(但这意味着如果其他事情意外失败,shell 继续进行,所以这可能有点危险),或者确保<em>left</em> && <em>right</em> 不会使 shell 退出。

有一种直接的方法可以实现后者:将 <em>left</em> && <em>right</em> 替换为if <em>离开</em>;然后<em>对</em>; fi:

if echo "$changed_files" | grep --quiet ""; then
    eval ""
fi

请注意,如果 eval "" 失败,shell 仍会退出。

有一种不同且略微棘手的方法可以实现不同的效果:替换 <em>left</em> && <em>right</em> <em>左</em> && <em>右</em> ||真。 "and" 表达式绑定得更紧密,所以这意味着:

  • 评价
  • 如果成功(退出零),评估正确
  • 如果 && 失败(left 退出非零,或者 right 是 运行 和 right exits nonzero), evaluate the || true part, exits 0.

因此:

echo "$changed_files" | grep --quiet "" && eval "" || true

总是退出 0,eval-ing "" 当且仅当左侧失败(没有找到 grepped-for 表达式)时。

如果你想让 check_runeval "" 失败的情况下继续耕作,请使用第二个 (|| true) 版本。如果您希望 check_runeval "" 失败时停止(并让整个 shell 退出)在 -e 下,请使用第一个 (if ...; then) 版本。


1真正古老的 4BSD /bin/sh,早在开源之前就有一个错误:-e would 在这些情况下使 shell 退出。 (我想我曾经自己修复过这个问题,但是当 4BSD 转向一个新的 sh 时,不是基于 Steve Bourne 的原始代码,这个错误根本就不存在了。)

2在bash中可以更详细的控制这个。特别是,您可以在 $PIPESTATUS 数组变量中获取管道的 每个 组件的状态。