Question

Setup

I've written a small script to do automatic pull/commit/push for some personal data. This script works great on my laptop (version 1.8.3.2-1) but on my server, the git pull command is failing with signal 13. I was running version 1.7.9.5-1 (these are both ubuntu, but the server is 12.04.4 LTS vs 13.10 on the laptop), I used the ppa:git-core repo to update git to 1.9.0-1~ppa0~precise1. This still gives the same problem with my script. The complication perhaps, is that the laptop pulls from a bare repo on the server over ssh while the server pulls from the same bare repo (to a working copy elsewhere) but doesn't use ssh, it just has the local path.

The problem

I can reproduce the problem in the terminal with the following command:

> 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

This seems to fetch the commit and check it out, but does not update the master branch and leaves the repo off branch:

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

Switching back to master gives a warning:

> 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'

My attempts to fix

Thinking the problem was due to stdout being a pipe but stderr being a tty, I tried:

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

But this does nothing at all and leaves ${msg} empty.

What does work is:

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

> git pull --quiet origin master

> git pull origin master

All of which do fetch the commit and update master. I'd like to capture output from git-pull in some cases so while I can fix up the repo, I'd like to know why this is happening.

So why can't I capture the output of git-pull, and why does it crash out leaving the repo in such a state?

Was it helpful?

Solution

First, your biggest problem here is a shell thing, which has little to do with git pull itself. Let's do something trivial:

$ echo foo | read msg; echo $msg

$ 

Why is $msg empty here? The answer has to do with pipelines, parsing, and sub-shells.


The basic syntax for shell commands is a series of "pipelines" separated by a series of semicolons and/or newlines. That is, given:

a | b; c

this parses much the same as:

(a | b); c

or exactly the same as:

a | b
c

Specifically, the b part is bound to the a part, and the c part comes later. Of course, adding explicit parentheses results in the use of a sub-shell, so you might realize instinctively that if the b part is a read command, the c part won't have the variable available, as the setting affects only the sub-shell, not the outer shell.

Alas, removing the parentheses does not help. It's true that this runs the a part in the main shell—but in order to read the output of the pipe, the b part is still run in a sub-shell. (In fact, absent some tricky optimizations, when you use the parentheses, the b part is run in a sub-sub-shell: a sub-shell of the explicitly-invoked sub-shell.)

This is why there is no way to access $msg after the piped-to part exits: the right hand side of any pipeline is always run in a sub-shell.


All is not quite lost: consider:

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

The trick here is to run the entire sequence, using $msg, inside the sub-shell. (Parentheses also work and the syntax is slightly less clumsy: echo foo | (read msg; echo $msg).)

Let's go back to the original attempt, and see about patching that up, e.g.:

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

This will likely do the same thing:

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

although the exact behavior depends on a lot of stuff. (In particular the "rewinding" message itself indicates that you have pull configured to run rebase, for instance.)

The signal 13 part tells us that git-pull is receiving a SIGPIPE error: writing on a broken pipe. What broken pipe could this be? The answer should be obvious as there is a pipe staring us in the face with the command itself:

git pull origin master | ...

A pipe "breaks" when the reader—the right hand side, read msg—exits before the writer (the LHS or git pull ...) is done, and the writer then writes something new. So this indicates that git pull is writing more than one line of output, because the read command reads one line and then exits (or, in the modified pipeline, reads one line, writes it to stdout, and then exits).

If you want to capture the output, you'll need to capture all of it:

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

for instance. (This no longer needs braces or parentheses as the while <list> do <list> done sequence parses as a single statement.) Or:

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

which will allow you to read the contents of the capture file /tmp/script.$$1 in the original shell process.


The not currently on any branch status occurs because the rebase got interrupted in the middle (due to the broken-pipe error). Rebase works by leaving the branch temporarily, accumulating new commits on a new, anonymous (unnamed "not-on-a-branch") branch, and then moving the original branch label so that the new-and-anonymous branch is now named, and the previously-named branch is abandoned. Adding --quiet stops all the output, so that read msg waits for the entire git pull sequence to finish (after which the read fails because there is no output after all).


1This really should use mktemp; the above is for illustration only.

OTHER TIPS

Note: one new feature from Git 2.2 (November 2014) might have an influence for this question, and avoid any signal 13 issue (maybe not exactly in your case, but more generally).

See commit 7559a1b from Patrick Reynolds (piki):

unblock and unignore SIGPIPE

Blocked and ignored signals -- but not caught signals -- are inherited across exec.
Some callers with sloppy signal-handling behavior can call git with SIGPIPE blocked or ignored, even non-deterministically.
When SIGPIPE is blocked or ignored, several git commands can run indefinitely, ignoring EPIPE returns from write() calls, even when the process that called them has gone away.
Our specific case involved a pipe of git diff-tree output to a script that reads a limited amount of diff data.

In an ideal world, git would never be called with SIGPIPE blocked or ignored.
But in the real world, several real potential callers, including Perl, Apache, and Unicorn, sometimes spawn subprocesses with SIGPIPE ignored.

It is easier and more productive to harden git against this mistake than to clean it up in every potential parent process.

Restore the way SIGPIPE is handled to default, which is what we expect.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top