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 left是echo ... | grep ...
,对是eval ""
.
我们看到左边部分 运行 而右边部分没有。这里是我们需要了解一些关于 shells 的地方:即使设置了 -e
,它们也不会 立即 在出现故障时退出,as只要那东西是测试的一部分.1 所以这不是直接的问题。
但还是有问题,因为<em>left</em> && <em>right</em>
有,因为它退出状态,最后一件事的退出状态运行s。它 运行 的最后一件事是 left 管道,即 echo ... | grep ...
。管道的退出状态是其最后一个组件的退出状态,2 即 grep
。如果 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_run
在 eval ""
失败的情况下继续耕作,请使用第二个 (|| true
) 版本。如果您希望 check_run
在 eval ""
失败时停止(并让整个 shell 退出)在 -e
下,请使用第一个 (if ...; then
) 版本。
1真正古老的 4BSD /bin/sh
,早在开源之前就有一个错误:-e
would 在这些情况下使 shell 退出。 (我想我曾经自己修复过这个问题,但是当 4BSD 转向一个新的 sh 时,不是基于 Steve Bourne 的原始代码,这个错误根本就不存在了。)
2在bash中可以更详细的控制这个。特别是,您可以在 $PIPESTATUS
数组变量中获取管道的 每个 组件的状态。
我有一个 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 left是echo ... | grep ...
,对是eval ""
.
我们看到左边部分 运行 而右边部分没有。这里是我们需要了解一些关于 shells 的地方:即使设置了 -e
,它们也不会 立即 在出现故障时退出,as只要那东西是测试的一部分.1 所以这不是直接的问题。
但还是有问题,因为<em>left</em> && <em>right</em>
有,因为它退出状态,最后一件事的退出状态运行s。它 运行 的最后一件事是 left 管道,即 echo ... | grep ...
。管道的退出状态是其最后一个组件的退出状态,2 即 grep
。如果 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_run
在 eval ""
失败的情况下继续耕作,请使用第二个 (|| true
) 版本。如果您希望 check_run
在 eval ""
失败时停止(并让整个 shell 退出)在 -e
下,请使用第一个 (if ...; then
) 版本。
1真正古老的 4BSD /bin/sh
,早在开源之前就有一个错误:-e
would 在这些情况下使 shell 退出。 (我想我曾经自己修复过这个问题,但是当 4BSD 转向一个新的 sh 时,不是基于 Steve Bourne 的原始代码,这个错误根本就不存在了。)
2在bash中可以更详细的控制这个。特别是,您可以在 $PIPESTATUS
数组变量中获取管道的 每个 组件的状态。