Skip to content

Commit

Permalink
feat: add mercurial support (#5)
Browse files Browse the repository at this point in the history
* Mercurial support (WIP)

* Fix tests

* Fix linting errors

* Add entry in README for hg and some new keywords in package.json

* Add husky-hg to README.md

* fix hg integration and update tests
  • Loading branch information
tobiastimm authored and azz committed Feb 10, 2018
1 parent 420fdec commit fca1264
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 2 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Runs [Prettier](https://prettier.io) on your changed files.
Supported source control managers:

* Git
* _Add more_
* Mercurial

## Install

Expand Down Expand Up @@ -54,6 +54,8 @@ With `npm`:

You can run `pretty-quick` as a pre-commit hook using [`husky`](https://github.com/typicode/husky).

> For Mercurial have a look at [`husky-hg`](https://github.com/TobiasTimm/husky-hg)
```shellstream
yarn add --dev husky
```
Expand Down
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
"main": "./dist",
"bin": "./bin/pretty-quick.js",
"license": "MIT",
"keywords": [
"git",
"mercurial",
"hg",
"prettier",
"pretty-quick",
"formatting",
"code",
"vcs",
"precommit"
],
"files": [
"bin",
"dist",
Expand Down
137 changes: 137 additions & 0 deletions src/__tests__/scm-hg.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import mock from 'mock-fs';
import execa from 'execa';
import fs from 'fs';

import prettyQuick from '..';

jest.mock('execa');

afterEach(() => {
mock.restore();
jest.clearAllMocks();
});

const mockHgFs = () => {
mock({
'/.hg': {},
'/foo.js': 'foo()',
'/bar.md': '# foo',
});
execa.sync.mockImplementation((command, args) => {
if (command !== 'hg') {
throw new Error(`unexpected command: ${command}`);
}
switch (args[0]) {
case 'status':
return { stdout: './foo.js\n' + './bar.md\n' };
case 'diff':
return { stdout: './foo.js\n' + './bar.md\n' };
case 'add':
return { stdout: '' };
case 'log':
return { stdout: '' };
default:
throw new Error(`unexpected arg0: ${args[0]}`);
}
});
};

describe('with hg', () => {
test('calls `hg debugancestor`', () => {
mock({
'/.hg': {},
});

prettyQuick('root');

expect(execa.sync).toHaveBeenCalledWith(
'hg',
['debugancestor', 'tip', 'default'],
{ cwd: '/' }
);
});

test('calls `hg debugancestor` with root hg directory', () => {
mock({
'/.hg': {},
'/other-dir': {},
});

prettyQuick('/other-dir');
expect(execa.sync).toHaveBeenCalledWith(
'hg',
['debugancestor', 'tip', 'default'],
{ cwd: '/' }
);
});

test('calls `hg status` with revision', () => {
mock({
'/.hg': {},
});

prettyQuick('root', { since: 'banana' });

expect(execa.sync).toHaveBeenCalledWith(
'hg',
['status', '-n', '-a', '-m', '--rev', 'banana'],
{ cwd: '/' }
);
});

test('calls onFoundSinceRevision with return value from `hg debugancestor`', () => {
const onFoundSinceRevision = jest.fn();

mock({
'/.hg': {},
});
execa.sync.mockReturnValue({ stdout: 'banana' });

prettyQuick('root', { onFoundSinceRevision });

expect(onFoundSinceRevision).toHaveBeenCalledWith('hg', 'banana');
});

test('calls onFoundChangedFiles with changed files', () => {
const onFoundChangedFiles = jest.fn();
mockHgFs();

prettyQuick('root', { since: 'banana', onFoundChangedFiles });

expect(onFoundChangedFiles).toHaveBeenCalledWith(['./foo.js', './bar.md']);
});

test('calls onWriteFile with changed files', () => {
const onWriteFile = jest.fn();
mockHgFs();

prettyQuick('root', { since: 'banana', onWriteFile });

expect(onWriteFile).toHaveBeenCalledWith('./foo.js');
expect(onWriteFile).toHaveBeenCalledWith('./bar.md');
});

test('writes formatted files to disk', () => {
const onWriteFile = jest.fn();

mockHgFs();

prettyQuick('root', { since: 'banana', onWriteFile });

expect(fs.readFileSync('/foo.js', 'utf8')).toEqual('formatted:foo()');
expect(fs.readFileSync('/bar.md', 'utf8')).toEqual('formatted:# foo');
});

test('without --staged does NOT stage changed files', () => {
mockHgFs();

prettyQuick('root', { since: 'banana' });

expect(execa.sync).not.toHaveBeenCalledWith('hg', ['add', './foo.js'], {
cwd: '/',
});
expect(execa.sync).not.toHaveBeenCalledWith('hg', ['add', './bar.md'], {
cwd: '/',
});
});
});
40 changes: 40 additions & 0 deletions src/scms/hg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import findUp from 'find-up';
import execa from 'execa';
import { dirname } from 'path';

export const name = 'hg';

export const detect = directory => {
const hgDirectory = findUp.sync('.hg', { cwd: directory });
if (hgDirectory) {
return dirname(hgDirectory);
}
};

const runHg = (directory, args) =>
execa.sync('hg', args, {
cwd: directory,
});

const getLines = execaResult => execaResult.stdout.split('\n');

export const getSinceRevision = (directory, { branch }) => {
const revision = runHg(directory, [
'debugancestor',
'tip',
branch || 'default',
]).stdout.trim();
return runHg(directory, ['id', '-i', '-r', revision]).stdout.trim();
};

export const getChangedFiles = (directory, revision) => {
return [
...getLines(
runHg(directory, ['status', '-n', '-a', '-m', '--rev', revision])
),
].filter(Boolean);
};

export const stageFile = (directory, file) => {
runHg(directory, ['add', file]);
};
3 changes: 2 additions & 1 deletion src/scms/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as gitScm from './git';
import * as hgScm from './hg';

const scms = [gitScm];
const scms = [gitScm, hgScm];

export default directory => {
for (const scm of scms) {
Expand Down

0 comments on commit fca1264

Please sign in to comment.