吉特·胡克无声地失败 [英] Git Hook Fails Silently

查看:90
本文介绍了吉特·胡克无声地失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个包含以下内容的结帐后和合并后的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"如果package.json没有更改

我是否缺少某些东西,导致check_run在找不到第一个文件更改时结束脚本?

视觉证明:

解决方案

这里的问题是set -e:-e的意思是如果失败则退出",其中失败"定义为退出非零"./p>

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

+ 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"
}

此解析为left && right,其中 left echo ... | grep ...,而 right eval "$2".

我们看到左侧部分跑了,而右侧部分没有跑.在这里,我们需要了解一些有关shell的知识:即使设置了-e,如果出现故障,它们也不会立即退出,只要它们是测试的一部分 >. 1 所以这不是立即出现的问题.

但这仍然是问题,因为left && right具有其最后运行的退出状态作为退出状态.它运行的最后一件事是 left 管道,它是echo ... | grep ....管道的退出状态是其最后一个组件 2 的退出状态,即grep.如果找到字符串,grep将退出零(并且使用--quiet也会抑制其输出),否则将返回1,如果出现一般错误则退出2,因此退出状态为1.

因此,left && right的退出状态也为1.

因此,由于-e生效,因此外壳退出了!

解决方法是避免使用-e(但这意味着如果其他意外操作失败,则外壳会犁开,因此可能有点危险),或者确保left && right不会使外壳变硬退出.

有一种简单的方法可以做到:用if left; then right; fi替换left && right:

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

请注意,如果eval "$2"失败,则外壳程序仍将退出.

有一种不同且有些棘手的方法来达到不同的效果:将left && right替换为left && right || true. "and"表达式的绑定更紧密,因此这意味着:

  • 评估
  • 如果成功(退出零),请评估 right
  • 如果&&失败( left 退出非零,或者运行 right right 退出非零),请评估部分,退出0.

因此:

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

仅当左侧发生故障(找不到grepped-for表达式)时,始终退出0,eval -ing "$2".

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


1 真正的古老4BSD /bin/sh早在开源之前就有一个bug:-e 在这种情况下退出外壳. (我想我自己修复了这个问题,但是当4BSD改用新版本而不是基于Steve Bourne的原始代码时,该错误根本就不存在了.)

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

I have a post-checkout and post-merge githook with these contents:

#!/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 

Strangely if there are no changes to .gitmodules, the script ends rather than progress to check the package.json. (it doesn't even execute the echo lines after line 12)

removing the check_run invocations and putting just the straight commands instead seems to work fine.

removing check_run .gitmodules "git submodule update --init --recursive" does work as well. However the same behavior is exhibited in the next line: check_run package.json "npm prune && npm install" if package.json has not been changed

Is there something that I am missing that causes check_run to end the script on the first file change not being found?

Visual Proof:

解决方案

The set -e is the problem here: -e means "exit if something fails", where "fails" is defined as "exits nonzero".

We see in the output, as the last few lines:

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

(and then nothing else). The script exited after grep --quiet.

Here's the actual definition of check_run:

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

This parses as left && right where left is the echo ... | grep ... and right is the eval "$2".

We see that the left part ran and the right part didn't. Here's where we need to know something about shells: even with -e set, they don't immediately exit if something fails, as long as that something is part of a test.1 So this isn't immediately the problem.

But it is still the problem, because left && right has, as its exit status, the exit status of the last thing it runs. The last thing it ran is the left pipeline, which was echo ... | grep .... The exit status of the pipeline is the exit status of its last component,2 i.e., grep. The grep exits zero if it finds the string (and with --quiet, suppresses its output as well), 1 if not, and 2 on generic errors, so the exit status was 1.

Therefore, the exit status of left && right was also 1.

Therefore, since -e was in effect, the shell exited!

The cure is either to avoid -e (but this means if something else fails unexpectedly, the shell plows on, so this might be a bit dangerous), or to make sure that the left && right does not make the shell exit.

There is one straightforward way to do the latter: replace left && right with if left; then right; fi:

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

Note that if eval "$2" fails, the shell will still exit.

There is a different and slightly tricky way to do this with a different effect: replace left && right with left && right || true. The "and" expression binds more tightly, so this means:

  • Evaluate left
  • If that succeeds (exits zero), evaluate right
  • If the && fails (either left exits nonzero, or right is run and right exits nonzero), evaluate the || true part, which exits 0.

Hence:

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

always exits 0, eval-ing "$2" if and only if the left side fails (does not find the grepped-for expression).

If you want check_run to plow on even if eval "$2" fails, use the second (|| true) version. If you want check_run to stop (and have the whole shell exit) under -e if eval "$2" fails, use the first (if ...; then) version.


1Truly ancient 4BSD /bin/sh, long before it went open source, had a bug: -e would make the shell exit in these cases. (I think I fixed that one myself at one point, but when 4BSD went to a new sh, not based on Steve Bourne's original code, the bug just wasn't there at all.)

2In bash, you can control this in more detail. In particular you can get the status of every component of a pipeline in the $PIPESTATUS array variable.

这篇关于吉特·胡克无声地失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆