Skip to content

Commit 7df937f

Browse files
committed
Pass standard input through to all hooks
Some git hooks can be passed additional information through standard input (e.g., the pre-push hook), so this commit handles that properly. Since stream data can be consumed only once but multiple hooks may need to use it, capture the stream data once and pipe it to each child process individually. Fixes tarmolov#26
1 parent 069d80a commit 7df937f

File tree

3 files changed

+94
-33
lines changed

3 files changed

+94
-33
lines changed

lib/git-hooks.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,10 @@ module.exports = {
115115
*
116116
* @param {String} filename Path to git hook.
117117
* @param {String[]} [args] Git hook arguments.
118+
* @param {String} input Input string to be passed into each hook's standard input.
118119
* @param {Function} callback
119120
*/
120-
run: function (filename, args, callback) {
121+
run: function (filename, args, input, callback) {
121122
var hookName = path.basename(filename);
122123
var hooksDirname = path.resolve(path.dirname(filename), '../../.githooks', hookName);
123124

@@ -127,7 +128,7 @@ module.exports = {
127128
return path.resolve(hooksDirname, hookName);
128129
});
129130
excludeIgnoredPaths(hooks, function (filteredHooks) {
130-
runHooks(filteredHooks, args, callback);
131+
runHooks(filteredHooks, args, input, callback);
131132
});
132133
} else {
133134
callback(0);
@@ -140,19 +141,20 @@ module.exports = {
140141
*
141142
* @param {String[]} hooks List of hook names to execute.
142143
* @param {String[]} args
144+
* @param {String} input Input string to be passed into each hook's standard input.
143145
* @param {Function} callback
144146
*/
145-
function runHooks(hooks, args, callback) {
147+
function runHooks(hooks, args, input, callback) {
146148
if (!hooks.length) {
147149
callback(0);
148150
return;
149151
}
150152

151153
try {
152-
var hook = spawnHook(hooks.shift(), args);
154+
var hook = spawnHook(hooks.shift(), args, input);
153155
hook.on('close', function (code) {
154156
if (code === 0) {
155-
runHooks(hooks, args, callback);
157+
runHooks(hooks, args, input, callback);
156158
} else {
157159
callback(code);
158160
}
@@ -177,15 +179,18 @@ function isExecutable(stats) {
177179
*
178180
* @param {String} hookName
179181
* @param {String[]} args
182+
* @param {String} input Input string to be passed into the hook's standard input.
180183
* @returns {ChildProcess}
181184
*/
182-
function spawnHook(hookName, args) {
185+
function spawnHook(hookName, args, input) {
183186
var stats = fs.statSync(hookName);
184187
var isHookExecutable = stats && stats.isFile() && isExecutable(stats);
185188
if (!isHookExecutable) {
186189
throw new Error('Cannot execute hook: ' + hookName + '. Please check file permissions.');
187190
}
188-
return spawn(hookName, args, {stdio: 'inherit'});
191+
var childProcess = spawn(hookName, args, {stdio: ['pipe', process.stdout, process.stderr]});
192+
childProcess.stdin.end(input, 'utf8');
193+
return childProcess;
189194
}
190195

191196
/**

lib/hook-template.js

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
#!/usr/bin/env node
22

3-
try {
4-
/**
5-
* require('git-hooks') isn't used to support case when node_modules is put in subdirectory.
6-
* .git
7-
* .githooks
8-
* www
9-
* node_modules
10-
*/
11-
require('%s/git-hooks').run(__filename, process.argv.slice(2), function (code, error) {
12-
if (error) {
13-
console.error('[GIT-HOOKS ERROR] ' + error.message);
3+
var Socket = require('net').Socket;
4+
var input = '';
5+
6+
if (process.stdin.isTTY) {
7+
runHooks();
8+
} else {
9+
if (process.stdin instanceof Socket) {
10+
process.stdin.setTimeout(300);
11+
process.stdin.on('timeout', runHooks);
12+
}
13+
14+
process.stdin.setEncoding('utf8');
15+
process.stdin.on('readable', function () {
16+
var chunk;
17+
while ((chunk = this.read()) !== null) {
18+
input += chunk;
1419
}
15-
process.exit(code);
1620
});
17-
} catch (e) {
18-
console.error('[GIT-HOOKS ERROR] ' + e.message);
21+
process.stdin.on('end', runHooks);
22+
}
23+
24+
function runHooks() {
25+
try {
26+
/**
27+
* require('git-hooks') isn't used to support case when node_modules is put in subdirectory.
28+
* .git
29+
* .githooks
30+
* www
31+
* node_modules
32+
*/
33+
require('%s/git-hooks').run(__filename, process.argv.slice(2), input, function (code, error) {
34+
if (error) {
35+
console.error('[GIT-HOOKS ERROR] ' + error.message);
36+
}
37+
process.exit(code);
38+
});
39+
} catch (e) {
40+
console.error('[GIT-HOOKS ERROR] ' + e.message);
1941

20-
if (e.code === 'MODULE_NOT_FOUND') {
21-
console.error('[GIT-HOOKS ERROR] Please reinstall git-hooks to fix this error');
42+
if (e.code === 'MODULE_NOT_FOUND') {
43+
console.error('[GIT-HOOKS ERROR] Please reinstall git-hooks to fix this error');
44+
}
2245
}
2346
}

tests/run.test.js

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require('chai').should();
22
var fs = require('fs');
3+
var exec = require('child_process').exec;
34
var gitHooks = require('../lib/git-hooks');
45
var fsHelpers = require('../lib/fs-helpers');
56

@@ -26,7 +27,7 @@ describe('git-hook runner', function () {
2627
});
2728

2829
it('should works without hooks', function (done) {
29-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
30+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
3031
code.should.equal(0);
3132
done();
3233
});
@@ -44,7 +45,7 @@ describe('git-hook runner', function () {
4445
});
4546

4647
it('should return an error', function (done) {
47-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code, error) {
48+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code, error) {
4849
code.should.equal(1);
4950
error.should.be.ok;
5051
done();
@@ -62,7 +63,7 @@ describe('git-hook runner', function () {
6263
});
6364

6465
it('should run it one by one', function (done) {
65-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
66+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
6667
code.should.equal(0);
6768
hooks.forEach(function (name) {
6869
var logFile = SANDBOX_PATH + name + '.log';
@@ -76,20 +77,52 @@ describe('git-hook runner', function () {
7677
describe('and work without errors', function () {
7778
var logFile = SANDBOX_PATH + 'hello.log';
7879
beforeEach(function () {
79-
createHook(PROJECT_PRECOMMIT_HOOK + 'hello', 'echo "Hello, world! ${@:1}" > ' + logFile);
80+
createHook(
81+
PROJECT_PRECOMMIT_HOOK + 'hello',
82+
'input=`cat`; echo -e "Hello, world!\n${@:1}\n$input" > ' + logFile
83+
);
8084
});
8185

8286
it('should pass all arguments to them', function (done) {
83-
gitHooks.run(PRECOMMIT_HOOK_PATH, ['I', 'am', 'working', 'properly!'], function () {
84-
fs.readFileSync(logFile).toString().should.equal('Hello, world! I am working properly!\n');
87+
gitHooks.run(PRECOMMIT_HOOK_PATH, ['I', 'am', 'working', 'properly!'], '', function () {
88+
fs.readFileSync(logFile).toString().trim().should.equal('Hello, world!\nI am working properly!');
8589
done();
8690
});
8791
});
8892

93+
describe('if standard input is not passed in', function () {
94+
it('should work properly', function (done) {
95+
exec(PRECOMMIT_HOOK_PATH, function () {
96+
fs.readFileSync(logFile).toString().trim()
97+
.should.equal('Hello, world!');
98+
done();
99+
});
100+
});
101+
});
102+
103+
describe('if standard input is passed in', function () {
104+
it('should read it properly', function (done) {
105+
exec('echo "I am working properly!" | ' + PRECOMMIT_HOOK_PATH, function () {
106+
fs.readFileSync(logFile).toString().trim()
107+
.should.equal('Hello, world!\n\nI am working properly!');
108+
done();
109+
});
110+
});
111+
112+
it('should pass it to them', function (done) {
113+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], 'I am working properly!', function () {
114+
fs.readFileSync(logFile).toString().trim()
115+
.should.equal('Hello, world!\n\nI am working properly!');
116+
done();
117+
});
118+
});
119+
120+
});
121+
89122
it('should run a hook with success status', function (done) {
90-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
123+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
91124
code.should.equal(0);
92-
fs.readFileSync(logFile).toString().should.equal('Hello, world! \n');
125+
fs.readFileSync(logFile).toString().trim().should.equal('Hello, world!');
93126
done();
94127
});
95128
});
@@ -101,7 +134,7 @@ describe('git-hook runner', function () {
101134
});
102135

103136
it('should run a hook and return error', function (done) {
104-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
137+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
105138
code.should.equal(255);
106139
done();
107140
});
@@ -119,7 +152,7 @@ describe('git-hook runner', function () {
119152
});
120153

121154
it('should ignore file with wrong permissions in hooks directory', function (done) {
122-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
155+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
123156
code.should.equal(0);
124157
done();
125158
});

0 commit comments

Comments
 (0)