From efccf007780b94b7092a3beeef50f8b8e88cdeba Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 17 Oct 2026 01:15:54 +0000 Subject: [PATCH] fix(state-matrix): detect reads the native diff, ignoring diff.external and textconv (review #3235 P1-4) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-state-matrix | 2 +- test/state-matrix.test.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bin/gstack-state-matrix b/bin/gstack-state-matrix index 1b5c9cfd9..f4d039b33 100755 --- a/bin/gstack-state-matrix +++ b/bin/gstack-state-matrix @@ -43,7 +43,7 @@ function detect(rest: string[]): number { const repoRoot = root.out.trim(); const base = resolveBaseBranch(repoRoot, flag(rest, '--base')); const mb = flag(rest, '--diff-base') ?? git(['merge-base', `origin/${base}`, 'HEAD'], repoRoot).out.trim(); - const diff = mb ? git(['diff', '--unified=0', '--no-color', mb], repoRoot) : { ok: false, out: '' }; + const diff = mb ? git(['-c', 'core.quotePath=false', 'diff', '--no-ext-diff', '--no-textconv', '--unified=0', '--no-color', mb], repoRoot) : { ok: false, out: '' }; if (!diff.ok) { process.stdout.write(`SCOPE_STATE_MACHINE=false\n# no merge base with origin/${base}; run: git fetch origin ${base}\n`); return EXIT.usage; } const { detected, hits } = detectStateMachine(addedLines(diff.out)); const lines = [`SCOPE_STATE_MACHINE=${detected}`, ...hits.slice(0, 12).map(h => `# state-machine: ${h.file}:${h.line} ${[...h.subject, ...h.action].join(',')}`)]; diff --git a/test/state-matrix.test.ts b/test/state-matrix.test.ts index e41b06b0a..59cab815a 100644 --- a/test/state-matrix.test.ts +++ b/test/state-matrix.test.ts @@ -47,6 +47,16 @@ describe('gstack-state-matrix detect', () => { expect(d.out.trim().split('\n').every(l => /^SCOPE_STATE_MACHINE=(true|false)$/.test(l) || l.startsWith('# '))).toBe(true); }); + test('review #3235 P1-4: diff.external and a textconv driver cannot hide the state machine from detect', () => { + const r = repo(); + r.git('config', 'diff.external', '/bin/true'); + expect(run(r.work, ['detect', '--base', 'main']).out.split('\n')[0]).toBe('SCOPE_STATE_MACHINE=true'); + r.git('config', '--unset', 'diff.external'); + fs.writeFileSync(path.join(r.work, '.git', 'info', 'attributes'), '*.ts diff=blank\n'); + r.git('config', 'diff.blank.textconv', 'true'); + expect(run(r.work, ['detect', '--base', 'main']).out.split('\n')[0]).toBe('SCOPE_STATE_MACHINE=true'); + }); + test('negative controls: a test-only change and words that merely contain "hold" or "accept" do not dispatch', () => { const r = repo(); r.git('checkout', '-q', '-b', 'unrelated', 'origin/main');