为什么git-pull会因信号13而失败?

时间:2014-03-18 22:03:43

标签: git bash shell git-pull

设置

我写了一个小脚本来自动提取/提交/推送一些个人数据。这个脚本在我的笔记本电脑(版本1.8.3.2-1)上工作得很好,但在我的服务器上,git pull命令失败并显示信号13.我运行的是版本1.7.9.5-1(这些都是ubuntu,但是服务器是12.04.4 LTS对比笔记本电脑上的13.10),我使用ppa:git-core repo将git更新为1.9.0-1~ppa0~precision1。这仍然会给我的脚本带​​来同样的问题。可能的复杂性是,笔记本电脑从服务器上的裸仓库通过ssh拉出,而服务器从相同的裸仓库(到其他地方的工作副本)拉出但不使用ssh,它只有本地路径。

问题

我可以使用以下命令在终端中重现问题:

> git pull origin master | read msg; echo ${msg}
First, rewinding head to replay your work on top of it...
error: git-pull died of signal 13

这似乎是获取提交并检查它,但是不更新主分支并使repo离开分支:

> git status
# Not currently on any branch.
nothing to commit (working directory clean)

切换回主人会发出警告:

> git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  7220b8f Commit message

If you want to keep them by creating a new branch, this may be a good time
to do so with:

 git branch new_branch_name 7220b8f5e2648ae49d3e3095e8bf942dfc41421c

Switched to branch 'master'

我尝试修复

认为这个问题是因为s​​tdout是一个管道,但是stderr是一个tty,我试过了:

> git pull origin master 2>&1 | read msg; echo ${msg}

但这根本不做任何事情,并使${msg}为空。

工作是什么:

> git pull --quiet origin master 2>&1 | read msg; echo ${msg}

> git pull --quiet origin master

> git pull origin master

所有这些都会获取提交和更新主服务器。我想在某些情况下捕获git-pull的输出,所以当我可以修复回购时,我想知道为什么会发生这种情况。

那么为什么我不能捕获git-pull的输出,为什么它会让repo处于这样的状态呢?

2 个答案:

答案 0 :(得分:7)

首先,你最大的问题是shell事物,它与git pull本身没什么关系。让我们做一些微不足道的事情:

$ echo foo | read msg; echo $msg

$ 

为什么$msg在这里为空?答案与管道,解析和子shell有关。


shell命令的基本语法是一系列"管道"由一系列分号和/或换行符分隔。那就是:

a | b; c

这与解析大致相同:

(a | b); c

或完全相同:

a | b
c

具体来说,b部分绑定到a部分,c部分后来绑定。当然,添加显式括号会导致使用子shell,因此您可能会本能地意识到,如果b部分是read命令,则c部分会赢得' t使变量可用,因为该设置仅影响子shell,而不影响外壳。

唉,删除括号无济于事。确实,这会在主shell中运行a部分 - 但是为了读取管道的输出,b部分仍然运行一个子壳。 (事实上​​,如果没有一些棘手的优化,当你使用括号时,b部分在子子shell中运行:显式调用的子shell的子shell。)

这就是在管道到部件退出后无法访问$msg的原因:任何管道的右侧总是在子壳中运行。


一切都不会丢失:考虑:

$ echo foo | { read msg; echo $msg; }
foo
$ 

这里的技巧是使用$msg在子shell中运行整个序列。 (括号也有效,语法稍微不那么笨拙:echo foo | (read msg; echo $msg)。)

让我们回到最初的尝试,看看有关修补的内容,例如:

git pull origin master | { read msg; echo ${msg}; }

这可能会做同样的事情:

First, rewinding head to replay your work on top of it...
error: git-pull died of signal 13

虽然确切的行为取决于很多东西。 (特别是"倒带"消息本身表示您已配置拉动以运行rebase。)

signal 13部分告诉我们git-pull收到SIGPIPE错误:写入损坏的管道。破碎管可能是什么?答案应该是显而易见的,因为有一个管道用命令本身盯着我们:

git pull origin master | ...

管道"断裂"当读者 - 右手边read msg - 在作者(LHS或git pull ...)完成之前退出,然后作者写下新的东西。所以这表明git pull正在写多行输出,因为read命令读取一行然后退出(或者,在修改后的管道中,读取一行,将其写入stdout,并且然后退出)。

如果您想捕获输出,则需要捕获所有

git pull origin master | while read msg; do ...; done
例如

。 (这不再需要大括号或括号,因为while <list> do <list> done序列作为单个语句进行解析。)或者:

git pull origin master > /tmp/script.$$

允许您在原始shell进程中读取捕获文件/tmp/script.$$ 1 的内容。


not currently on any branch状态发生是因为rebase在中间被中断(由于管道错误)。 Rebase的工作原理是暂时离开分支,在新的匿名(未命名的&#34; not-on-a-branch&#34;)分支上累积新的提交,然后移动原始分支标签,以便新的和匿名的现在命名为branch,并且放弃了先前命名的分支。添加--quiet会停止所有输出,以便read msg等待整个git pull序列完成(之后read失败,因为毕竟没有输出。)< / p>


1 这真的应该使用mktemp;以上仅供参考。

答案 1 :(得分:3)

注意:Git 2.2中的一个新功能(2014年11月)可能会对此问题产生影响,并避免出现任何信号问题(可能不完全符合您的情况,但更常见)。

请参阅commit 7559a1b中的Patrick Reynolds (piki)

取消阻止和取消SIGPIPE

  

阻止和忽略信号 - 但未捕获信号 - 在exec之间继承   一些信号处理行为不稳定的呼叫者可以在SIGPIPE被阻止或忽略的情况下调用git,甚至是非确定性的。   当SIGPIPE被阻止或被忽略时,几个git命令可以无限期地运行,忽略EPIPE来自write()次调用的返回,即使调用它们的进程已经消失。   我们的具体案例涉及到一个读取有限数量差异数据的脚本的git diff-tree输出管道。

     

在一个理想的世界中,绝不会在SIGPIPE被阻止或忽略的情况下调用git   但在现实世界中,包括Perl,Apache和Unicorn在内的几个真正潜在的调用者有时会产生忽略SIGPIPE的子进程。

     

强化git来应对这个错误比在每个潜在的父进程中清理它更容易,更有效率。      

将SIGPIPE的处理方式恢复为默认值,这正是我们所期望的