Git Hook 静默失败

标签 git git-submodules githooks post-checkout-hook

我有一个包含这些内容的 check out 后和 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 "$1" && eval "$2"
}

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 在未找到第一个文件更改时结束脚本?

视觉证明: enter image description here

最佳答案

set -e是这里的问题:-e表示“如果失败则退出”,其中“失败”定义为“非零退出”。

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

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

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

这是 check_run 的实际定义:

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

这解析为 <em>left</em> && <em>right</em> 左边echo ... | grep ...eval "$2" .

我们看到左边的部分运行了,右边的部分没有运行。这是我们需要了解一些关于 shell 的地方:即使是 -e设置后,如果出现故障,它们不会立即退出,只要那是测试的一部分1 所以这不是立即解决问题。

但还是有问题,因为<em>left</em> && <em>right</em>有,作为它的退出状态,它运行的最后一个东西的退出状态。它运行的最后一件事是 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>left</em>; then <em>right</em>; fi :

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

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

有一种不同且略微棘手的方法可以达到不同的效果:替换 <em>left</em> && <em>right</em><em>left</em> && <em>right</em> || true . “and”表达式结合得更紧密,所以这意味着:

  • 评估左边
  • 如果成功(退出零),评估正确
  • 如果&&失败(left 退出非零,或者 right 运行并且 right 退出非零),评估 || true部分,退出 0。

因此:

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

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

如果你想要check_run即使eval "$2"也要耕种失败,使用第二个(|| true)版本。如果你想要check_run-e 下停止(并让整个 shell 退出)如果eval "$2"失败,使用第一个(if ...; then)版本。


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

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

关于Git Hook 静默失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43878982/

相关文章:

git - 如何 `git submodule add` 现有子存储库?

node.js - 如何 cd 到文件夹并使用 git hook post-receive 运行 npm start ?

haskell - 部署使用 Snap 框架的 Haskell 代码

git - 如何在一个 Git 仓库中实现/管理多个项目?

Git 嵌套 repo

python - 如何在特定日期从 github pip 安装

git - 从 svn 迁移到 git 保持 svn-externals

Git子模块到子文件夹

Git 扩展 "Create new repository"不工作

python - 使用预提交 Hook 防止提交 pdb 或 pytest set_trace