Skip to content

Commit d594ec4

Browse files
authored
fix(core): determine git root correctly in sub directories (#64)
* test(cli): add failing integration test * fix: determine git root correctly from sub directories * fixup! make git initializing work fixes #62
1 parent e6d3b3c commit d594ec4

File tree

5 files changed

+117
-27
lines changed

5 files changed

+117
-27
lines changed

@commitlint/cli/cli.test.js

+82-20
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,135 @@
11
import path from 'path';
22
import test from 'ava';
33
import execa from 'execa';
4+
import {sync as bin} from 'resolve-bin';
5+
import * as sander from 'sander';
46
import stream from 'string-to-stream';
7+
import tmp from 'tmp';
58

69
const here = path.join.bind(null, __dirname);
10+
const fix = here.bind(null, 'fixtures');
711

8-
const SIMPLE = here('fixtures/simple');
9-
const EXTENDS_ROOT = here('fixtures/extends-root');
10-
const EMPTY = here('fixtures/empty');
11-
12-
const cli = (input = '', args = [], opts = {}) => {
13-
const c = execa(here('cli.js'), args, {
14-
capture: ['stdout'],
15-
cwd: opts.cwd
16-
});
17-
stream(input).pipe(c.stdin);
18-
return c;
12+
const CLI = here('cli.js');
13+
const SIMPLE = fix('simple');
14+
const EXTENDS_ROOT = fix('extends-root');
15+
const EMPTY = fix('empty');
16+
17+
const HUSKY = tmp.dirSync().name;
18+
const HUSKY_INTEGRATION = path.join(tmp.dirSync().name, 'integration');
19+
20+
const exec = (command, args = [], opts = {}) => {
21+
return async (input = '') => {
22+
const c = execa(command, args, {
23+
capture: ['stdout'],
24+
cwd: opts.cwd
25+
});
26+
stream(input).pipe(c.stdin);
27+
const result = await c;
28+
if (result.code !== 0) {
29+
console.log(result.stderr);
30+
}
31+
return result;
32+
}
1933
};
2034

35+
const cli = exec.bind(null, CLI);
36+
const git = exec.bind(null, 'git');
37+
const mkdir = exec.bind(null, bin('mkdirp'));
38+
const npm = exec.bind(null, 'npm');
39+
const rm = exec.bind(null, bin('rimraf'));
40+
2141
test('should throw when called without [input]', t => {
22-
t.throws(cli(), /Expected a raw commit/);
42+
t.throws(cli()(), /Expected a raw commit/);
2343
});
2444

2545
test('should reprint input from stdin', async t => {
26-
const actual = await cli('foo: bar', [], {cwd: EMPTY});
46+
const actual = await cli([], {cwd: EMPTY})('foo: bar');
2747
t.true(actual.stdout.includes('foo: bar'));
2848
});
2949

3050
test('should produce no success output with --quiet flag', async t => {
31-
const actual = await cli('foo: bar', ['--quiet'], {cwd: EMPTY});
51+
const actual = await cli(['--quiet'], {cwd: EMPTY})('foo: bar');
3252
t.is(actual.stdout, '');
3353
t.is(actual.stderr, '');
3454
});
3555

3656
test('should produce no success output with -q flag', async t => {
37-
const actual = await cli('foo: bar', ['-q'], {cwd: EMPTY});
57+
const actual = await cli(['-q'], {cwd: EMPTY})('foo: bar');
3858
t.is(actual.stdout, '');
3959
t.is(actual.stderr, '');
4060
});
4161

4262
test('should succeed for input from stdin without rules', async t => {
43-
const actual = await cli('foo: bar', [], {cwd: EMPTY});
63+
const actual = await cli([], {cwd: EMPTY})('foo: bar');
4464
t.is(actual.code, 0);
4565
});
4666

4767
test('should fail for input from stdin with rule from rc', async t => {
48-
const actual = await t.throws(cli('foo: bar', [], {cwd: SIMPLE}));
68+
const actual = await t.throws(cli([], {cwd: SIMPLE})('foo: bar'));
4969
t.true(actual.stdout.includes('type must not be one of [foo]'));
5070
t.is(actual.code, 1);
5171
});
5272

5373
test('should fail for input from stdin with rule from js', async t => {
5474
const actual = await t.throws(
55-
cli('foo: bar', ['--extends', './extended'], {cwd: EXTENDS_ROOT})
75+
cli(['--extends', './extended'], {cwd: EXTENDS_ROOT})('foo: bar')
5676
);
5777
t.true(actual.stdout.includes('type must not be one of [foo]'));
5878
t.is(actual.code, 1);
5979
});
6080

6181
test('should produce no error output with --quiet flag', async t => {
62-
const actual = await t.throws(cli('foo: bar', ['--quiet'], {cwd: SIMPLE}));
82+
const actual = await t.throws(cli(['--quiet'], {cwd: SIMPLE})('foo: bar'));
6383
t.is(actual.stdout, '');
6484
t.is(actual.stderr, '');
6585
t.is(actual.code, 1);
6686
});
6787

6888
test('should produce no error output with -q flag', async t => {
69-
const actual = await t.throws(cli('foo: bar', ['-q'], {cwd: SIMPLE}));
89+
const actual = await t.throws(cli(['-q'], {cwd: SIMPLE})('foo: bar'));
7090
t.is(actual.stdout, '');
7191
t.is(actual.stderr, '');
7292
t.is(actual.code, 1);
7393
});
94+
95+
test('should work with husky commitmsg hook', async () => {
96+
const cwd = HUSKY;
97+
98+
await init(cwd);
99+
await pkg(cwd);
100+
101+
await npm(['install', 'husky'], {cwd})();
102+
await git(['add', 'package.json'], {cwd})();
103+
await git(['commit', '-m', '"chore: this should work"'], {cwd})();
104+
105+
await rm([HUSKY])();
106+
});
107+
108+
test('should work with husky commitmsg hook in sub packages', async () => {
109+
const cwd = HUSKY_INTEGRATION;
110+
const upper = path.dirname(HUSKY_INTEGRATION);
111+
112+
await mkdir([cwd])();
113+
await init(upper);
114+
await pkg(cwd);
115+
116+
await npm(['install', 'husky'], {cwd})();
117+
await git(['add', 'package.json'], {cwd})();
118+
119+
await git(['commit', '-m', '"chore: this should work"'], {cwd})();
120+
121+
await rm([upper])();
122+
});
123+
124+
async function init(cwd) {
125+
await git(['init'], {cwd})();
126+
127+
return Promise.all([
128+
git(['config', 'user.email', '"[email protected]"'], {cwd})(),
129+
git(['config', 'user.name', '"commitlint"'], {cwd})()
130+
]);
131+
}
132+
133+
function pkg(cwd) {
134+
return sander.writeFile(cwd, 'package.json', JSON.stringify({scripts: {commitmsg: `${CLI} -e`}}));
135+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"scripts":{"commitmsg":"commitlint -e"}}

@commitlint/cli/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@
4444
"ava": "^0.18.2",
4545
"dependency-check": "^2.9.1",
4646
"execa": "^0.7.0",
47+
"mkdirp": "^0.5.1",
48+
"resolve-bin": "^0.4.0",
49+
"rimraf": "^2.6.1",
50+
"sander": "^0.6.0",
4751
"string-to-stream": "^1.1.0",
52+
"tmp": "0.0.33",
4853
"xo": "^0.18.2"
4954
},
5055
"dependencies": {

@commitlint/core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@
134134
"chalk": "^2.0.1",
135135
"conventional-changelog-angular": "^1.3.3",
136136
"conventional-commits-parser": "^1.3.0",
137+
"find-up": "^2.1.0",
137138
"franc": "^2.0.0",
138139
"git-raw-commits": "^1.1.2",
139-
"git-toplevel": "^1.1.1",
140140
"import-from": "^2.1.0",
141141
"lodash": "^4.17.4",
142142
"mz": "^2.6.0",

@commitlint/core/src/read.js

+28-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {join} from 'path';
1+
import path from 'path';
22
import exists from 'path-exists';
3+
import up from 'find-up';
34
import gitRawCommits from 'git-raw-commits';
4-
import gitToplevel from 'git-toplevel';
55
import {readFile} from 'mz/fs';
66

77
export default getCommitMessages;
@@ -45,16 +45,38 @@ function getHistoryCommits(options) {
4545
// Check if the current repository is shallow
4646
// () => Promise<Boolean>
4747
async function isShallow() {
48-
const top = await gitToplevel();
49-
const shallow = join(top, '.git/shallow');
48+
const top = await toplevel();
49+
50+
if (typeof top !== 'string') {
51+
throw new TypeError(`Could not find git root - is this a git repository?`);
52+
}
53+
54+
const shallow = path.join(top, '.git/shallow');
5055
return exists(shallow);
5156
}
5257

5358
// Get recently edited commit message
5459
// () => Promise<Array<String>>
5560
async function getEditCommit() {
56-
const top = await gitToplevel();
57-
const editFilePath = join(top, '.git/COMMIT_EDITMSG');
61+
const top = await toplevel();
62+
63+
if (typeof top !== 'string') {
64+
throw new TypeError(`Could not find git root - is this a git repository?`);
65+
}
66+
67+
const editFilePath = path.join(top, '.git/COMMIT_EDITMSG');
5868
const editFile = await readFile(editFilePath);
5969
return [`${editFile.toString('utf-8')}\n`];
6070
}
71+
72+
// Find the next git root
73+
// (start: string) => Promise<string | null>
74+
async function toplevel(cwd = process.cwd()) {
75+
const found = await up('.git', {cwd});
76+
77+
if (typeof found !== 'string') {
78+
return found;
79+
}
80+
81+
return path.join(found, '..');
82+
}

0 commit comments

Comments
 (0)