Skip to content

Fix/warn about shallow clones #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/marionebl/conventional-changelog-lint.git"
"url": "https://github.com/marionebl/conventional-changelog-lint.git"
},
"bugs": {
"url": "https://github.com/marionebl/conventional-changelog-lint/issues"
Expand Down
98 changes: 75 additions & 23 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ resolves `extends` configurations.

```shell
❯ conventional-changelog-lint --help
conventional-changelog-lint@0.1.0 - Lint commit messages against a conventional-changelog preset and ruleset
conventional-changelog-lint - Lint commit messages against a conventional-changelog preset and ruleset

[input] reads from stdin if --edit, --from, --to are omitted
--color,-c toggle formatted output, defaults to: true
Expand All @@ -42,6 +42,41 @@ resolves `extends` configurations.

```

### Recipes

#### git hook
As a `commitmsg` git-hook with ["husky"](https://git.io/JDwyQg)

```json
{
"scripts": {
"commitmsg": "conventional-changelog-lint -e"
}
}
```


#### Last commit
As part of `npm test`

```json
{
"scripts": {
"test": "conventional-changelog-lint --from=HEAD~1"
}
}
```

#### Travis

```yml
# Force full git checkout
before_install: git fetch --unshallow

# Lint all commits not in the target branch
before_script: conventional-changelog-lint --from=$TRAVIS_BRANCH to=$TRAVIS_PULL_REQUEST_BRANCH
```

### API

The programming interface does not read configuration by default,
Expand Down Expand Up @@ -77,28 +112,6 @@ const report = lint(
);
```

### Recipes

* As a `commitmsg` git-hook with ["husky"](https://git.io/JDwyQg)

```json
{
"scripts": {
"commitmsg": "conventional-changelog-lint -e"
}
}
```

* As part of `npm test`

```json
{
"scripts": {
"test": "conventional-changelog-lint --from=HEAD~1"
}
}
```

## Configuration

`conventional-changelog-lint` is configured via
Expand Down Expand Up @@ -186,6 +199,45 @@ wildcards: {
}
```

## Shallow clones

### TL;DR

Perform `git fetch --shallow` before linting.

Most likely you are reading this because you where presented with an error message:

```
'Could not get git history from shallow clone.
Use git fetch --shallow before linting.
Original issue: https://git.io/vyKMq\n Refer to https://git.io/vyKMv for details.'
```

### Explanation

git supports checking out `shallow` clones of a repository to save bandwith in times.
These limited copies do not contain a full git history. This makes `conventional-changelog-lint`
fail, especially when running on large commit ranges.
To ensure linting works every time you should convert a shallow git repo to a complete one.
Use `git fetch --shallow` to do so.

### Travis

Ensure full git checkouts on TravisCI, add to `.travis.yml`:

```yml
before_install:
- git fetch --unshallow
```

### Appveyor

Ensure full git checkouts on AppVeyor, add to `appveyor.yml`:

```yml
shallow_clone: false
```

## Supported Node.js versions

conventional-changelog-lint supports the active Node.js [LTS](https://github.com/nodejs/LTS#lts-schedule) version and higher: `>= 4`
Expand Down
19 changes: 19 additions & 0 deletions source/library/get-messages.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import {join} from 'path';
import exists from 'path-exists';
import gitRawCommits from 'git-raw-commits';
import gitToplevel from 'git-toplevel';
import {readFile} from 'mz/fs';

export default getCommitMessages;

const SHALLOW_MESSAGE = [
'Could not get git history from shallow clone.',
'Use git fetch --shallow before linting.',
'Original issue: https://git.io/vyKMq\n Refer to https://git.io/vyKMv for details.'
].join('\n');

// Get commit messages
// Object => Promise<Array<String>>
async function getCommitMessages(settings) {
Expand All @@ -14,6 +21,10 @@ async function getCommitMessages(settings) {
return getEditCommit();
}

if (await isShallow()) {
throw new Error(SHALLOW_MESSAGE);
}

return await getHistoryCommits({from, to});
}

Expand All @@ -31,6 +42,14 @@ function getHistoryCommits(options) {
});
}

// Check if the current repository is shallow
// () => Promise<Boolean>
async function isShallow() {
const top = await gitToplevel();
const shallow = join(top, '.git/shallow');
return await exists(shallow);
}

// Get recently edited commit message
// () => Promise<Array<String>>
async function getEditCommit() {
Expand Down
60 changes: 47 additions & 13 deletions test/integration/get-messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,36 @@ import rimraf from 'rimraf';
import expect from 'unexpected';

import getMessages from '../../source/library/get-messages';
import pkg from '../../package';

const rm = denodeify(rimraf);

test.serial('get edit commit message from git root', async () => {
const repo = await initRepository();
test.beforeEach(async t => {
t.context.repos = [await initRepository()];
});

test.afterEach.always(async t => {
try {
await Promise.all(t.context.repos.map(async repo => cleanRepository(repo)));
t.context.repos = [];
} catch (err) {
console.log({err});
}
});

test.serial('get edit commit message from git root', async t => {
const [repo] = t.context.repos;

await writeFile('alpha.txt', 'alpha');
await execa('git', ['add', '.']);
await execa('git', ['commit', '-m', 'alpha']);

const expected = ['alpha\n\n'];
const actual = await getMessages({edit: true});
expect(actual, 'to equal', expected);

await cleanRepository(repo);
});

test.serial('get history commit messages', async () => {
const repo = await initRepository();
test.serial('get history commit messages', async t => {
const [repo] = t.context.repos;

await writeFile('alpha.txt', 'alpha');
await execa('git', ['add', 'alpha.txt']);
Expand All @@ -40,12 +51,10 @@ test.serial('get history commit messages', async () => {
const expected = ['remove alpha\n\n', 'alpha\n\n'];
const actual = await getMessages({});
expect(actual, 'to equal', expected);

await cleanRepository(repo);
});

test.serial('get edit commit message from git subdirectory', async () => {
const repo = await initRepository();
test.serial('get edit commit message from git subdirectory', async t => {
const [repo] = t.context.repos;

await mkdir('beta');
await writeFile('beta/beta.txt', 'beta');
Expand All @@ -56,8 +65,20 @@ test.serial('get edit commit message from git subdirectory', async () => {
const expected = ['beta\n\n'];
const actual = await getMessages({edit: true});
expect(actual, 'to equal', expected);
});

test.serial('get history commit messages from shallow clone', async t => {
const [repo] = t.context.repos;

await writeFile('alpha.txt', 'alpha');
await execa('git', ['add', 'alpha.txt']);
await execa('git', ['commit', '-m', 'alpha']);

const clone = await cloneRepository(pkg.repository.url, repo, '--depth', '1');
t.context.repos = [...t.context.repos, clone];

await cleanRepository(repo);
const err = await t.throws(getMessages({from: 'master'}));
expect(err.message, 'to contain', 'Could not get git history from shallow clone');
});

async function initRepository() {
Expand All @@ -74,8 +95,21 @@ async function initRepository() {
return {directory, previous};
}

async function cloneRepository(source, context, ...args) {
const directory = join(tmpdir(), rand());
await execa('git', ['clone', ...args, source, directory]);
process.chdir(directory);

await execa('git', ['config', 'user.email', '[email protected]']);
await execa('git', ['config', 'user.name', 'ava']);

return {directory, previous: context.previous};
}

async function cleanRepository(repo) {
process.chdir(repo.previous);
if (repo.previous && repo.previous !== process.cwd()) {
process.chdir(repo.previous);
}

if (await exists(repo.directory)) {
await rm(repo.directory);
Expand Down