Skip to content

Commit a7607c0

Browse files
committedJun 23, 2015
Initial commit
0 parents  commit a7607c0

16 files changed

+541
-0
lines changed
 

‎.githooks/pre-commit/test

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
#
3+
# * Check changed js files using jshint and jscs.
4+
# * Runs tests.
5+
#
6+
7+
PATCH_FILE="working-tree.patch"
8+
NPM_BIN="./node_modules/.bin"
9+
10+
function cleanup {
11+
exit_code=$?
12+
if [ -f "$PATCH_FILE" ]; then
13+
git apply "$PATCH_FILE" 2> /dev/null
14+
rm "$PATCH_FILE"
15+
fi
16+
exit $exit_code
17+
}
18+
19+
trap cleanup EXIT SIGINT SIGHUP
20+
21+
# Cancel any changes to the working tree that are not going to be committed
22+
git diff > "$PATCH_FILE"
23+
git checkout -- .
24+
25+
git_cached_files=$(git diff --cached --name-only --diff-filter=ACMR | xargs echo)
26+
if [ "$git_cached_files" ]; then
27+
npm test --silent || exit 1
28+
fi

‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules/*
2+
!/node_modules/git-hooks
3+
npm-debug.log

‎.jscs.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"preset": "yandex",
3+
4+
"excludeFiles": [
5+
"node_modules/**",
6+
".git/**"
7+
]
8+
}

‎.jshintignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

‎.jshintrc

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"curly": true,
3+
"eqeqeq": true,
4+
"freeze": true,
5+
"immed": true,
6+
"maxlen": 120,
7+
"newcap": true,
8+
"noarg": true,
9+
"noempty": true,
10+
"nonbsp": true,
11+
"nonew": true,
12+
"quotmark": "single",
13+
"trailing": true,
14+
"undef": true,
15+
"unused": true,
16+
"node": true,
17+
18+
"overrides": {
19+
"**.test.js": {
20+
"mocha": true,
21+
"expr": true
22+
}
23+
}
24+
}

‎CONTRIBUTION.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## Pull requests and Code contributions
2+
3+
* Tests must pass.
4+
* Follow [our coding style](https://github.com/yandex/codestyle/blob/master/javascript.md) (jscs and jshint will help you).
5+
* If you fix a bug, add a test.
6+
* If you can't fix a bug, file an [issue](https://github.com/tarmolov/git-hooks-js/issues/new) with the steps to reproduce, the expected and the actual results.
7+
8+
## Library structure
9+
```
10+
.git-hooks Git hooks
11+
bin Executable file
12+
lib Library code
13+
tests Tests
14+
15+
## How to develop
16+
### Create your own copy of bla
17+
```
18+
git clone https://github.com/tarmolov/git-hooks.git
19+
npm install
20+
```
21+
**Note.** It's better to create a fork, if you plan to make a pull request.
22+
23+
### Run tests
24+
```
25+
npm test
26+
```

‎README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# git-hooks-js
2+
3+
It's inspired by [git-hooks](https://github.com/icefox/git-hooks) but has several differences:
4+
5+
* Created for nodejs projects and written in nodejs not bash.
6+
* Installs and removes hooks automatically during the package installation/uninstallation.
7+
* Finds hooks only in `.githooks` directory in the project.
8+
* Should be installed as a local package and of course it would work only for git repositories.
9+
* Well-tested and ready for use :)
10+
11+
## Installation
12+
Run in the root of your project.
13+
```
14+
npm install git-hooks --save-dev
15+
mkdir .githooks
16+
```
17+
See example of a hook in [.githooks](https://github.com/tarmolov/git-hooks-js/tree/master/.githooks) directory.

‎bin/git-hooks

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env node
2+
3+
var program = require('commander');
4+
var cli = require('../lib/cli');
5+
6+
program
7+
.version(require('../package.json').version)
8+
.usage('[options]')
9+
.description('A tool to manage project Git hooks')
10+
.option('--install', 'Replace existing hooks in this repository with a call git-hooks. Move old hooks directory to hooks.old')
11+
.option('--uninstall', 'Remove existing hooks in this repository and rename hooks.old back to hooks')
12+
.parse(process.argv);
13+
14+
cli(program);

‎lib/cli.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var gitHooks = require('../lib/git-hooks');
2+
3+
module.exports = function (program) {
4+
var command = program.install && 'install' || program.uninstall && 'uninstall';
5+
6+
if (!gitHooks[command]) {
7+
return program.outputHelp();
8+
}
9+
10+
gitHooks[command]().fail(function (error) {
11+
console.error(error.message);
12+
});
13+
};

‎lib/git-hooks.js

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
var util = require('util');
2+
var path = require('path');
3+
var spawn = require('child_process').spawn;
4+
var vow = require('vow');
5+
var vowFs = require('vow-fs');
6+
var config = require('../package.json').gitHooks;
7+
8+
module.exports = {
9+
/**
10+
* Installs git hooks.
11+
*/
12+
install: function (currentPath) {
13+
return getClosestPath(config.dirs.root, currentPath).then(function (gitPath) {
14+
var hooksPath = path.resolve(gitPath, config.dirs.hooks);
15+
var hooksOldPath = path.resolve(gitPath, config.dirs.hooksOld);
16+
17+
return vow.all([
18+
vowFs.exists(hooksPath),
19+
vowFs.exists(hooksOldPath)
20+
])
21+
.spread(function (isHooksExist, isHooksOldExist) {
22+
if (isHooksOldExist) {
23+
throw new Error('git-hooks already installed');
24+
}
25+
26+
if (isHooksExist) {
27+
return vowFs.move(hooksPath, hooksOldPath);
28+
}
29+
})
30+
.then(function () {
31+
return vowFs.makeDir(hooksPath);
32+
})
33+
.then(function () {
34+
return vow.all(
35+
config.hooks.map(function (hookName) {
36+
var hookPath = path.resolve(hooksPath, hookName);
37+
return vowFs.write(hookPath, config.template.join('\n'), {mode: '0777'});
38+
})
39+
);
40+
});
41+
});
42+
},
43+
44+
/**
45+
* Uninstall git hooks.
46+
*/
47+
uninstall: function (currentPath) {
48+
return getClosestPath(config.dirs.root, currentPath).then(function (gitPath) {
49+
var hooksPath = path.resolve(gitPath, config.dirs.hooks);
50+
var hooksOldPath = path.resolve(gitPath, config.dirs.hooksOld);
51+
52+
return vow.all([
53+
vowFs.exists(hooksPath),
54+
vowFs.exists(hooksOldPath)
55+
])
56+
.spread(function (isHooksExist, isHooksOldExist) {
57+
if (!isHooksExist) {
58+
throw new Error('git-hooks is not installed');
59+
}
60+
61+
return vowFs.removeDir(hooksPath).then(function () {
62+
if (isHooksOldExist) {
63+
return vowFs.move(hooksOldPath, hooksPath);
64+
}
65+
});
66+
});
67+
});
68+
},
69+
70+
/**
71+
* Runs a git hook.
72+
*
73+
* @param {String} filename Path to git hook.
74+
* @param {String} [arg] Git hook argument.
75+
*/
76+
run: function (filename, arg) {
77+
var hookName = path.basename(filename);
78+
var hooksDirname = path.resolve(path.dirname(filename), '../../.githooks', hookName);
79+
80+
return vowFs.exists(hooksDirname).then(function (isExists) {
81+
if (isExists) {
82+
return vowFs.listDir(hooksDirname).then(function (list) {
83+
var hooks = list.map(function (hookName) {
84+
return path.resolve(hooksDirname, hookName);
85+
});
86+
return runHooks(hooks, [arg]);
87+
});
88+
}
89+
});
90+
}
91+
};
92+
93+
/**
94+
* Runs hooks.
95+
*
96+
* @param {String[]} hooks List of hook names to execute.
97+
* @param {String[]} args
98+
* @param {vow.Deferred} [defer]
99+
*/
100+
function runHooks(hooks, args, defer) {
101+
defer = defer || vow.defer();
102+
if (!hooks.length) {
103+
return defer.resolve(0);
104+
}
105+
106+
var hook = spawn(hooks.shift(), args, {stdio: 'inherit'});
107+
hook.on('close', function (code) {
108+
if (code === 0) {
109+
runHooks(hooks, args, defer);
110+
} else {
111+
defer.reject(code);
112+
}
113+
});
114+
return defer.promise();
115+
}
116+
117+
/**
118+
* Returns the closest directory.
119+
* It starts looking from the current directory and does it up to the fs root.
120+
* It returns undefined in case where the specified directory isn't found.
121+
*
122+
* @param {String} dirname Directory name to look for.
123+
* @param {String} [currentPath] Current started path to search.
124+
* @returns {String|undefined}
125+
*/
126+
function getClosestPath(dirname, currentPath) {
127+
currentPath = currentPath || __dirname;
128+
129+
// reaches ths fs root
130+
if (currentPath === '/') {
131+
return vow.reject(new Error(util.format('Directory %s is not found', dirname)));
132+
}
133+
134+
var dirnamePath = path.join(currentPath, dirname);
135+
return vowFs.exists(dirnamePath).then(function (isExists) {
136+
return isExists ?
137+
dirnamePath :
138+
getClosestPath(dirname, path.resolve(currentPath, '..'));
139+
});
140+
}

‎node_modules/git-hooks/README.md

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎node_modules/git-hooks/package.json

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"name": "git-hooks",
3+
"description": "A tool to manage project Git hooks",
4+
"author": "Alexander Tarmolov <tarmolov@gmail.com>",
5+
"version": "1.0.0-rc.1",
6+
"repository": "https://github.com/tarmolov/git-hooks-js",
7+
"bin": {
8+
"git-hooks": "./bin/git-hooks"
9+
},
10+
"scripts": {
11+
"postinstall": "git-hooks --install",
12+
"preuninstall": "git-hooks --uninstall",
13+
"test": "jscs . && jshint . && mocha --reporter spec --recursive tests"
14+
},
15+
"main": "lib/git-hooks",
16+
"dependencies": {
17+
"commander": "^2.8.1",
18+
"vow": "~0.4.10",
19+
"vow-fs": "~0.3.4"
20+
},
21+
"devDependencies": {
22+
"chai": "^2.3.0",
23+
"jscs": "^1.13.1",
24+
"jshint": "^2.8.0",
25+
"mocha": "^2.2.5"
26+
},
27+
"gitHooks": {
28+
"dirs": {
29+
"root": ".git",
30+
"hooks": "hooks",
31+
"hooksOld": "hooks.old"
32+
},
33+
"hooks": [
34+
"applypatch-msg",
35+
"commit-msg",
36+
"post-applypatch",
37+
"post-checkout",
38+
"post-commit",
39+
"post-merge",
40+
"post-receive",
41+
"pre-applypatch",
42+
"pre-auto-gc",
43+
"pre-commit",
44+
"pre-push",
45+
"pre-rebase",
46+
"pre-receive",
47+
"prepare-commit-msg",
48+
"update"
49+
],
50+
"template": [
51+
"#!/usr/bin/env node",
52+
"try {",
53+
" require('git-hooks').run(__filename, process.argv[2]).fail(function () {",
54+
" process.exit(-1);",
55+
" });",
56+
"} catch (e) {",
57+
" console.log('Cannot find git-hooks. Did you install it?');",
58+
"}"
59+
]
60+
}
61+
}

‎tests/install.test.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require('chai').should();
2+
var vowFs = require('vow-fs');
3+
var gitHooks = require('../lib/git-hooks');
4+
5+
var config = require('../package.json').gitHooks;
6+
var SANDBOX_PATH = __dirname + '/tmp-sandbox/';
7+
var GIT_ROOT = SANDBOX_PATH + '.git/';
8+
9+
describe('--install', function () {
10+
beforeEach(function () {
11+
return vowFs.makeDir(GIT_ROOT);
12+
});
13+
14+
afterEach(function () {
15+
return vowFs.removeDir(SANDBOX_PATH);
16+
});
17+
18+
it('should install hooks', function () {
19+
return gitHooks.install(SANDBOX_PATH).then(function () {
20+
return vowFs.exists(GIT_ROOT + config.dirs.hooks).then(function (isExists) {
21+
isExists.should.be.true;
22+
});
23+
});
24+
});
25+
26+
describe('when some hooks already exist', function () {
27+
beforeEach(function () {
28+
return vowFs.makeDir(GIT_ROOT + config.dirs.hooks);
29+
});
30+
31+
it('should backup hooks before installation', function () {
32+
return gitHooks.install(SANDBOX_PATH).then(function () {
33+
return vowFs.exists(GIT_ROOT + config.dirs.hooksOld).then(function (isExists) {
34+
isExists.should.be.true;
35+
});
36+
});
37+
});
38+
});
39+
40+
describe('when git-hooks is already installed', function () {
41+
beforeEach(function () {
42+
return vowFs.makeDir(GIT_ROOT + config.dirs.hooksOld);
43+
});
44+
45+
it('should show error', function (done) {
46+
gitHooks.install(SANDBOX_PATH).fail(function () {
47+
done();
48+
});
49+
});
50+
});
51+
});

‎tests/run.test.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
require('chai').should();
2+
var vow = require('vow');
3+
var vowFs = require('vow-fs');
4+
var gitHooks = require('../lib/git-hooks');
5+
6+
var config = require('../package.json').gitHooks;
7+
var SANDBOX_PATH = __dirname + '/tmp-sandbox/';
8+
var GIT_ROOT = SANDBOX_PATH + '.git/';
9+
var PRECOMMIT_HOOK_PATH = GIT_ROOT + config.dirs.hooks + '/pre-commit';
10+
var GITHOOKS_PATH = SANDBOX_PATH + '.githooks/pre-commit/';
11+
12+
function createHook(path, content) {
13+
return vowFs.write(path, '#!/bin/bash\n' + content).then(function () {
14+
return vowFs.chmod(path, '0777');
15+
});
16+
}
17+
18+
describe('git-hook runner', function () {
19+
beforeEach(function () {
20+
return vowFs.makeDir(GIT_ROOT).then(function () {
21+
return gitHooks.install(SANDBOX_PATH);
22+
});
23+
});
24+
25+
afterEach(function () {
26+
return vowFs.removeDir(SANDBOX_PATH);
27+
});
28+
29+
it('should works without hooks', function () {
30+
return gitHooks.run(PRECOMMIT_HOOK_PATH);
31+
});
32+
33+
describe('when a hooks are found', function () {
34+
beforeEach(function () {
35+
return vowFs.makeDir(GITHOOKS_PATH);
36+
});
37+
38+
describe('more than one', function () {
39+
var hooks = ['foo', 'bar', 'baz'];
40+
beforeEach(function () {
41+
return vow.all(hooks.map(function (name) {
42+
var logFile = SANDBOX_PATH + name + '.log';
43+
return createHook(GITHOOKS_PATH + name, 'echo ' + name + '> ' + logFile);
44+
}));
45+
});
46+
47+
it('should run it one by one', function () {
48+
return gitHooks.run(PRECOMMIT_HOOK_PATH).then(function () {
49+
return vow.all(hooks.map(function (name) {
50+
var logFile = SANDBOX_PATH + name + '.log';
51+
return vowFs.read(logFile).then(function (data) {
52+
data.toString().should.equal(name + '\n');
53+
});
54+
}));
55+
});
56+
});
57+
});
58+
59+
describe('and works without errors', function () {
60+
var logFile = SANDBOX_PATH + 'hello.log';
61+
beforeEach(function () {
62+
return createHook(GITHOOKS_PATH + 'hello', 'echo Hello, world! > ' + logFile);
63+
});
64+
65+
it('should run a hook and resolve a promise', function () {
66+
return gitHooks.run(PRECOMMIT_HOOK_PATH).then(function () {
67+
return vowFs.read(logFile).then(function (data) {
68+
data.toString().should.equal('Hello, world!\n');
69+
});
70+
});
71+
});
72+
});
73+
74+
describe('and the hook finished with an error', function () {
75+
beforeEach(function () {
76+
return createHook(GITHOOKS_PATH + 'hello', 'exit -1');
77+
});
78+
79+
it('should run a hook and reject promise with an error', function (done) {
80+
gitHooks.run(PRECOMMIT_HOOK_PATH).fail(function (status) {
81+
status.should.equal(255);
82+
done();
83+
});
84+
});
85+
});
86+
});
87+
});

‎tests/uninstall.test.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
require('chai').should();
2+
var vow = require('vow');
3+
var vowFs = require('vow-fs');
4+
var gitHooks = require('../lib/git-hooks');
5+
6+
var config = require('../package.json').gitHooks;
7+
var SANDBOX_PATH = __dirname + '/tmp-sandbox/';
8+
var GIT_ROOT = SANDBOX_PATH + '.git/';
9+
10+
describe('--uninstall', function () {
11+
beforeEach(function () {
12+
return vowFs.makeDir(GIT_ROOT);
13+
});
14+
15+
afterEach(function () {
16+
return vowFs.removeDir(SANDBOX_PATH);
17+
});
18+
19+
describe('when git-hooks is not installed', function () {
20+
it('should show an error', function (done) {
21+
return gitHooks.uninstall(SANDBOX_PATH).fail(function () {
22+
done();
23+
});
24+
});
25+
});
26+
27+
describe('when git-hooks is installed', function () {
28+
beforeEach(function () {
29+
return vowFs.makeDir(GIT_ROOT + config.dirs.hooks);
30+
});
31+
32+
it('should remove hooks directory', function () {
33+
return gitHooks.uninstall(SANDBOX_PATH).then(function () {
34+
return vowFs.exists(GIT_ROOT + config.dirs.hooks).then(function (isExists) {
35+
isExists.should.be.false;
36+
});
37+
});
38+
});
39+
});
40+
41+
describe('when backup exists', function () {
42+
beforeEach(function () {
43+
return vow.all([
44+
vowFs.makeDir(GIT_ROOT + config.dirs.hooks),
45+
vowFs.makeDir(GIT_ROOT + config.dirs.hooksOld)
46+
]);
47+
});
48+
49+
it('should move it to hooks directory', function () {
50+
return gitHooks.uninstall(SANDBOX_PATH).then(function () {
51+
return vowFs.exists(GIT_ROOT + config.dirs.hooks).then(function (isExists) {
52+
isExists.should.be.true;
53+
});
54+
});
55+
});
56+
});
57+
});

0 commit comments

Comments
 (0)
Please sign in to comment.