diff --git a/scripts/hooks/block-no-verify.js b/scripts/hooks/block-no-verify.js index 138075484..ecd29100c 100644 --- a/scripts/hooks/block-no-verify.js +++ b/scripts/hooks/block-no-verify.js @@ -248,7 +248,25 @@ function getCommitShortValueOption(value) { } function isCommitNoVerifyShortFlag(value) { - return value === '-n' || /^-n[a-zA-Z]/.test(value); + if (!value.startsWith('-') || value.startsWith('--') || value === '-') { + return false; + } + + // Short options cluster, so -n need not lead: `git commit -an` is -a plus -n + // and bypasses the hooks just as `-n` does. Anchoring on the first character + // let -an, -sn and -vn through. + // + // Scanning stops at a value-taking option because that option swallows the + // rest of the cluster as its inline value — the n in `-mn` is message text, + // not a flag. + const options = value.slice(1); + for (let i = 0; i < options.length; i++) { + const option = options.charAt(i); + if (option === 'n') return true; + if (COMMIT_SHORT_OPTIONS_WITH_VALUE.has(option)) return false; + } + + return false; } /** diff --git a/tests/hooks/block-no-verify.test.js b/tests/hooks/block-no-verify.test.js index f610030c6..db38dbb19 100644 --- a/tests/hooks/block-no-verify.test.js +++ b/tests/hooks/block-no-verify.test.js @@ -115,6 +115,28 @@ if (test('allows -n after combined -am message option', () => { assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}: ${r.stderr}`); })) passed++; else failed++; +// --- Short options cluster, so -n need not lead --- + +if (test('blocks -n clustered after -a', () => { + const r = runHook({ tool_input: { command: 'git commit -an -m "msg"' } }); + assert.strictEqual(r.code, 2, `expected exit 2, got ${r.code}`); +})) passed++; else failed++; + +if (test('blocks -n clustered after -s', () => { + const r = runHook({ tool_input: { command: 'git commit -sn -m "msg"' } }); + assert.strictEqual(r.code, 2, `expected exit 2, got ${r.code}`); +})) passed++; else failed++; + +if (test('blocks -n clustered after -v', () => { + const r = runHook({ tool_input: { command: 'git commit -vn -m "msg"' } }); + assert.strictEqual(r.code, 2, `expected exit 2, got ${r.code}`); +})) passed++; else failed++; + +if (test('allows -mn, where n is the inline message and not a flag', () => { + const r = runHook({ tool_input: { command: 'git commit -mn' } }); + assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}: ${r.stderr}`); +})) passed++; else failed++; + if (test('allows core.hooksPath discussed in a quoted commit message', () => { const r = runHook({ tool_input: { command: 'git commit -m "doc: explain core.hooksPath= setting"' } }); assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}: ${r.stderr}`);