Skip to content

Commit 8146487

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.
1 parent 3723414 commit 8146487

File tree

3 files changed

+65
-37
lines changed

3 files changed

+65
-37
lines changed

lib/git-hooks.js

Lines changed: 13 additions & 8 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
}
@@ -175,17 +177,20 @@ function isExecutable(stats) {
175177
/**
176178
* Spawns hook as a separate process.
177179
*
178-
* @param {String} hookName
180+
* @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', 'inherit', 'inherit']});
192+
childProcess.stdin.end(input, 'utf8');
193+
return childProcess;
189194
}
190195

191196
/**

lib/hook-template.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
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);
14-
}
15-
process.exit(code);
16-
});
17-
} catch (e) {
18-
console.error('[GIT-HOOKS ERROR] ' + e.message);
3+
var input = '';
4+
5+
process.stdin.setEncoding('utf8');
6+
7+
process.stdin.on('readable', function () {
8+
var chunk;
9+
if ((chunk = process.stdin.read()) !== null) {
10+
input += chunk;
11+
}
12+
});
13+
14+
process.stdin.on('end', function () {
15+
try {
16+
/**
17+
* require('git-hooks') isn't used to support case when node_modules is put in subdirectory.
18+
* .git
19+
* .githooks
20+
* www
21+
* node_modules
22+
*/
23+
require('%s/git-hooks').run(__filename, process.argv.slice(2), input, function (code, error) {
24+
if (error) {
25+
console.error('[GIT-HOOKS ERROR] ' + error.message);
26+
}
27+
process.exit(code);
28+
});
29+
} catch (e) {
30+
console.error('[GIT-HOOKS ERROR] ' + e.message);
1931

20-
if (e.code === 'MODULE_NOT_FOUND') {
21-
console.error('[GIT-HOOKS ERROR] Please reinstall git-hooks to fix this error');
32+
if (e.code === 'MODULE_NOT_FOUND') {
33+
console.error('[GIT-HOOKS ERROR] Please reinstall git-hooks to fix this error');
34+
}
2235
}
23-
}
36+
});

tests/run.test.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('git-hook runner', function () {
2626
});
2727

2828
it('should works without hooks', function (done) {
29-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
29+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
3030
code.should.equal(0);
3131
done();
3232
});
@@ -44,7 +44,7 @@ describe('git-hook runner', function () {
4444
});
4545

4646
it('should return an error', function (done) {
47-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code, error) {
47+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code, error) {
4848
code.should.equal(1);
4949
error.should.be.ok;
5050
done();
@@ -62,7 +62,7 @@ describe('git-hook runner', function () {
6262
});
6363

6464
it('should run it one by one', function (done) {
65-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
65+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
6666
code.should.equal(0);
6767
hooks.forEach(function (name) {
6868
var logFile = SANDBOX_PATH + name + '.log';
@@ -76,20 +76,30 @@ describe('git-hook runner', function () {
7676
describe('and work without errors', function () {
7777
var logFile = SANDBOX_PATH + 'hello.log';
7878
beforeEach(function () {
79-
createHook(PROJECT_PRECOMMIT_HOOK + 'hello', 'echo "Hello, world! ${@:1}" > ' + logFile);
79+
createHook(
80+
PROJECT_PRECOMMIT_HOOK + 'hello',
81+
'input=`cat`; echo -e "Hello, world!\n${@:1}\n$input" > ' + logFile
82+
);
8083
});
8184

8285
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');
86+
gitHooks.run(PRECOMMIT_HOOK_PATH, ['I', 'am', 'working', 'properly!'], '', function () {
87+
fs.readFileSync(logFile).toString().should.equal('Hello, world!\nI am working properly!\n\n');
88+
done();
89+
});
90+
});
91+
92+
it('should pass standard input to them', function (done) {
93+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], 'I am working properly!', function () {
94+
fs.readFileSync(logFile).toString().should.equal('Hello, world!\n\nI am working properly!\n');
8595
done();
8696
});
8797
});
8898

8999
it('should run a hook with success status', function (done) {
90-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
100+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
91101
code.should.equal(0);
92-
fs.readFileSync(logFile).toString().should.equal('Hello, world! \n');
102+
fs.readFileSync(logFile).toString().should.equal('Hello, world!\n\n\n');
93103
done();
94104
});
95105
});
@@ -101,7 +111,7 @@ describe('git-hook runner', function () {
101111
});
102112

103113
it('should run a hook and return error', function (done) {
104-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
114+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
105115
code.should.equal(255);
106116
done();
107117
});
@@ -119,7 +129,7 @@ describe('git-hook runner', function () {
119129
});
120130

121131
it('should ignore file with wrong permissions in hooks directory', function (done) {
122-
gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) {
132+
gitHooks.run(PRECOMMIT_HOOK_PATH, [], '', function (code) {
123133
code.should.equal(0);
124134
done();
125135
});

0 commit comments

Comments
 (0)