I have three git repositories:
I set a post-receive, a server side hook, on the bare repository to have the repository(B) pull from the bare and have the git server send a notification email when I commit and push from the repository(A) to the bare repository. The post-receive file I made on the bare repository is as follows:
#!/bin/sh
ssh -l MYUSER -i PRIVKEY www.HOSTINGSERVER.com "cd GITDIRECTORY;git pull"
. $(dirname $0)/post-receive-email
And I also set environmental variables on the bare repository:
git config hooks.mailinglist "MyMailAddress"
git config hooks.announcelist "MyMailAddress"
git config hooks.emailprefix "FooBar"
echo FooBar > description
I expected the repository(B) pulled and the git server sent a notification email by post-receive-email when I commit and push to the bare repository but the post-receive didn't work as I hoped. It had the repository(B) pull but didn't have the git server send any email. I didn't see any email error in logs on the git server.
When I commented out the ssh part of the post-receive, I got an email. When I commented out the post-receive-email part, the repository(B) pulled correctly. When both the ssh part and the post-receive-email part are active, the post-receive only have the repository(B) pull and didn't send any email.
I don't know why the post-receive doesn't work as I expect when both the ssh part and the post-receive-email part exist at the same time.
Any hint and comment is appreciated. Thank you in advance.
TL;DR: ssh -n
.
Background facts:
The post-receive hook gets input on its standard input. Reading stdin, you will get one line per updated reference, in the form described in the githooks documentation.
All of the Git hooks that receive input on stdin do so on a pipe. (I contend that this is a bad idea—that they should receive their input as a file descriptor open on a temporary file—but that's a separate battle. Even if that were not the case you would still need another trick.)
Reading the input available on a pipe consumes that input. Once it's read, it's gone.
(This, by the way, is why the read
built-in in shells is so inefficient: it's necessary to read the input one byte at a time, so that only as much as read
is going to read is consumed.)
With that in mind, consider the ssh
command, and this excerpt from its documentation:
-n Redirects stdin from /dev/null (actually, prevents reading from stdin).
So, what happens if you run ssh
without -n
? It will, of course, read stdin. But ... how much will it read? Well, strictly speaking, that depends: ssh runs several processes and/or threads, and there is a race if there is a great deal of stdin input and the command run on the remote end executes very quickly, but in general, the amount that ssh reads is all of stdin.
Now consult background fact #3, and answer the question: what's left on stdin, when ssh finishes?
Now, think about what your post-receive hook that sends email does. It must, necessarily, read its stdin to see which references were updated. What data will it get on its stdin?
If you prevent ssh
from reading (and thereby consuming) all the input, the post-receive hook will be able to read (and consume, but now it doesn't matter any more) all the input, and therefore function correctly.