diff --git a/@commitlint/cli/fixtures/comment-char/commitlint.config.js b/@commitlint/cli/fixtures/comment-char/commitlint.config.js index 2292580640..98ea1686e0 100644 --- a/@commitlint/cli/fixtures/comment-char/commitlint.config.js +++ b/@commitlint/cli/fixtures/comment-char/commitlint.config.js @@ -1,6 +1,6 @@ module.exports = { rules: { - 'subject-empty': [2, 'never'] + 'body-empty': [2, 'never'] }, parserPreset: { parserOpts: { diff --git a/@commitlint/cli/package.json b/@commitlint/cli/package.json index a7d95c50b6..ddc77537bc 100644 --- a/@commitlint/cli/package.json +++ b/@commitlint/cli/package.json @@ -41,7 +41,6 @@ "@commitlint/utils": "^17.0.0", "@types/node": "12.20.52", "@types/yargs": "^17.0.0", - "execa": "^5.0.0", "fs-extra": "^10.0.0" }, "dependencies": { @@ -50,6 +49,7 @@ "@commitlint/load": "^17.0.0", "@commitlint/read": "^17.0.0", "@commitlint/types": "^17.0.0", + "execa": "^5.0.0", "lodash": "^4.17.19", "resolve-from": "5.0.0", "resolve-global": "1.0.0", diff --git a/@commitlint/cli/src/cli.test.ts b/@commitlint/cli/src/cli.test.ts index 324c55e409..9f628f4d58 100644 --- a/@commitlint/cli/src/cli.test.ts +++ b/@commitlint/cli/src/cli.test.ts @@ -329,13 +329,50 @@ test('should handle --amend with signoff', async () => { expect(commit).toBeTruthy(); }, 10000); -test('should fail with an empty message and a commentChar is set', async () => { +test('it uses parserOpts.commentChar when not using edit mode', async () => { + const cwd = await gitBootstrap('fixtures/comment-char'); + const input = 'header: foo\n$body\n'; + + const actual = await cli([], {cwd})(input); + expect(actual.stdout).toContain('[body-empty]'); + expect(actual.exitCode).toBe(1); +}); + +test("it doesn't use parserOpts.commentChar when using edit mode", async () => { + const cwd = await gitBootstrap('fixtures/comment-char'); + await fs.writeFile( + path.join(cwd, '.git', 'COMMIT_EDITMSG'), + 'header: foo\n\n$body\n' + ); + + const actual = await cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); + expect(actual.stdout).not.toContain('[body-empty]'); + expect(actual.exitCode).toBe(0); +}); + +test('it uses core.commentChar git config when using edit mode', async () => { const cwd = await gitBootstrap('fixtures/comment-char'); await execa('git', ['config', '--local', 'core.commentChar', '$'], {cwd}); - await fs.writeFile(path.join(cwd, '.git', 'COMMIT_EDITMSG'), '#1234'); + await fs.writeFile( + path.join(cwd, '.git', 'COMMIT_EDITMSG'), + 'header: foo\n\n$body\n' + ); + + const actual = await cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); + expect(actual.stdout).toContain('[body-empty]'); + expect(actual.exitCode).toBe(1); +}); + +test('it falls back to # for core.commentChar when using edit mode', async () => { + const cwd = await gitBootstrap('fixtures/comment-char'); + await fs.writeFile( + path.join(cwd, '.git', 'COMMIT_EDITMSG'), + 'header: foo\n\n#body\n' + ); const actual = await cli(['--edit', '.git/COMMIT_EDITMSG'], {cwd})(); - expect(actual.stdout).toContain('[subject-empty]'); + expect(actual.stdout).toContain('[body-empty]'); + expect(actual.stderr).toEqual(''); expect(actual.exitCode).toBe(1); }); diff --git a/@commitlint/cli/src/cli.ts b/@commitlint/cli/src/cli.ts index e95d31536a..4f9fd5bd21 100644 --- a/@commitlint/cli/src/cli.ts +++ b/@commitlint/cli/src/cli.ts @@ -1,3 +1,4 @@ +import execa, {ExecaError} from 'execa'; import load from '@commitlint/load'; import lint from '@commitlint/lint'; import read from '@commitlint/read'; @@ -21,6 +22,8 @@ import {CliError} from './cli-error'; const pkg = require('../package'); +const gitDefaultCommentChar = '#'; + const cli = yargs .options({ color: { @@ -221,11 +224,24 @@ async function main(args: MainArgs) { } const format = loadFormatter(loaded, flags); - // Strip comments if reading from `.git/COMMIT_EDIT_MSG` using the - // commentChar from the parser preset falling back to a `#` if that is not - // set - if (flags.edit && typeof opts.parserOpts.commentChar !== 'string') { - opts.parserOpts.commentChar = '#'; + // If reading from `.git/COMMIT_EDIT_MSG`, strip comments using + // core.commentChar from git configuration, falling back to '#'. + if (flags.edit) { + try { + const {stdout} = await execa('git', ['config', 'core.commentChar']); + opts.parserOpts.commentChar = stdout.trim() || gitDefaultCommentChar; + } catch (e) { + const execaError = e as ExecaError; + // git config returns exit code 1 when the setting is unset, + // don't warn in this case. + if (!execaError.failed || execaError.exitCode !== 1) { + console.warn( + 'Could not determine core.commentChar git configuration', + e + ); + } + opts.parserOpts.commentChar = gitDefaultCommentChar; + } } const results = await Promise.all( diff --git a/package.json b/package.json index c529210375..dc58ad41aa 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "publish": "lerna publish --conventional-commits", "reinstall": "yarn clean && yarn install", "start": "yarn watch", - "test": "jest", - "test-ci": "jest --runInBand", + "test": "cross-env HOME=$PWD jest", + "test-ci": "cross-env HOME=$PWD jest --runInBand", "postinstall": "yarn husky install" }, "commitlint": { @@ -90,6 +90,7 @@ "@types/node": "^12.20.27", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", + "cross-env": "^7.0.3", "docsify-cli": "^4.4.3", "eslint": "^8.0.0", "eslint-config-prettier": "^8.0.0", @@ -106,4 +107,4 @@ "resolutions": { "**/lodash": "^4.17.19" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 199b7b8a08..411bb83369 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3915,7 +3915,14 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==