Skip to content

Commit f9063b7

Browse files
committed
feat: refuse to run when files were staged with --intent-to-add
1 parent a767299 commit f9063b7

9 files changed

Lines changed: 140 additions & 37 deletions

File tree

‎.changeset/curvy-trains-joke.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'lint-staged': minor
3+
---
4+
5+
_Lint-staged_ now refuses to run when files were staged with `--intent-to-add`, because Git stash doesn't support them. Previously this was an unhandled error.

‎lib/getStagedFiles.js‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const getStagedFiles = async ({ all, cwd, gitConfigDir, diff, diffFilter
101101
*
102102
* @see https://git-scm.com/docs/git-diff#_raw_output_format
103103
*/
104-
const output = await execGit([...command, '--raw', '-z'], { cwd })
104+
const output = await execGit([...command, '--ita-visible-in-index', '--raw', '-z'], { cwd })
105105
return parseGitDiffOutput(output, cwd)
106106
}
107107

@@ -127,3 +127,14 @@ export const getStagedFiles = async ({ all, cwd, gitConfigDir, diff, diffFilter
127127
return null
128128
}
129129
}
130+
131+
export const getAddedFilesWithoutIta = async ({ cwd, diff, diffFilter }) => {
132+
const addedWithoutIta = await execGit(
133+
[...getDiffCommand(diff, diffFilter), '--ita-invisible-in-index', '--raw', '-z'],
134+
{ cwd }
135+
)
136+
137+
return parseGitZOutput(addedWithoutIta).map((filename) =>
138+
normalizePath(path.resolve(cwd, filename))
139+
)
140+
}

‎lib/index.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createDebug, enableDebug } from './debug.js'
44
import { execGit } from './execGit.js'
55
import {
66
gitError,
7+
intentToAddNotSupported,
78
minGitVersionRequired,
89
noConfiguration,
910
preventedEmptyCommit,
@@ -20,6 +21,7 @@ import {
2021
ConfigNotFoundError,
2122
FailOnChangesError,
2223
GitError,
24+
IntentToAddError,
2325
RestoreUnstagedChangesError,
2426
} from './symbols.js'
2527
import { validateOptions } from './validateOptions.js'
@@ -99,6 +101,8 @@ const lintStaged = async (options = {}, logger = console) => {
99101

100102
if (ctx.errors.has(ConfigNotFoundError)) {
101103
logger.error(noConfiguration())
104+
} else if (ctx.errors.has(IntentToAddError)) {
105+
logger.error(intentToAddNotSupported(runAllError.cause))
102106
} else if (ctx.errors.has(ApplyEmptyCommitError)) {
103107
logger.warn(preventedEmptyCommit())
104108
} else if (ctx.errors.has(FailOnChangesError)) {

‎lib/messages.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ export const notGitRepo = () => red(`${figures.error()} Current directory is not
1414

1515
export const failedGetStagedFiles = () => red(`${figures.error()} Failed to get staged files!`)
1616

17+
export const intentToAddNotSupported = (files) =>
18+
`${red(`${figures.error()} lint-staged refused to run with files staged with "--intent-to-add":`)}
19+
20+
${files.map((f) => `- ${f}`).join('\n')}
21+
`
22+
1723
export const incorrectBraces = (before, after) =>
1824
yellow(
1925
`${figures.warning()} Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\`

‎lib/runAll.js‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { generateTasks } from './generateTasks.js'
1010
import { getAbortController } from './getAbortController.js'
1111
import { getFunctionTask, isFunctionTask } from './getFunctionTask.js'
1212
import { getSpawnedTasks } from './getSpawnedTasks.js'
13-
import { getStagedFiles } from './getStagedFiles.js'
13+
import { getAddedFilesWithoutIta, getStagedFiles } from './getStagedFiles.js'
1414
import { GitWorkflow } from './gitWorkflow.js'
1515
import { groupFilesByConfig } from './groupFilesByConfig.js'
1616
import {
@@ -37,7 +37,12 @@ import {
3737
shouldRestoreUntrackedFiles,
3838
updateIndexSkipped,
3939
} from './state.js'
40-
import { ConfigNotFoundError, GetStagedFilesError, GitRepoError } from './symbols.js'
40+
import {
41+
ConfigNotFoundError,
42+
GetStagedFilesError,
43+
GitRepoError,
44+
IntentToAddError,
45+
} from './symbols.js'
4146

4247
const debugLog = createDebug('lint-staged:runAll')
4348

@@ -172,6 +177,17 @@ export const runAll = async (
172177
return ctx
173178
}
174179

180+
/** If there are added files, re-check without `--intent-to-add` because Git stash doesn't support them */
181+
if (stagedFiles.some((f) => f.status === 'A')) {
182+
const addedFiles = stagedFiles.flatMap((f) => (f.status === 'A' ? f.filepath : []))
183+
const addedWithoutIta = await getAddedFilesWithoutIta({ cwd: topLevelDir, diff, diffFilter })
184+
const itaFiles = addedFiles.filter((f) => !addedWithoutIta.includes(f))
185+
if (itaFiles.length > 0) {
186+
ctx.errors.add(IntentToAddError)
187+
throw createError(ctx, itaFiles)
188+
}
189+
}
190+
175191
const numberOfConfigs = Object.keys(foundConfigs).length
176192

177193
// Throw if no configurations were found

‎lib/symbols.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export const ConfigEmptyError = new Error('Configuration should not be empty')
88

99
export const GetStagedFilesError = Symbol('GetStagedFilesError')
1010

11+
export const IntentToAddError = Symbol('IntentToAddError')
12+
1113
export const GitError = Symbol('GitError')
1214

1315
export const GitRepoError = Symbol('GitRepoError')
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import path from 'node:path'
2+
3+
import { describe, test } from 'vitest'
4+
5+
import { normalizePath } from '../../lib/normalizePath.js'
6+
import { oxfmtListDifferent } from './__fixtures__/configs.js'
7+
import { prettyJS } from './__fixtures__/files.js'
8+
import { withGitIntegration } from './__utils__/withGitIntegration.js'
9+
10+
describe('lint-staged', () => {
11+
test(
12+
'refuses to run when files staged with "--intent-to-add"',
13+
withGitIntegration(async ({ writeFile, execGit, expect, gitCommit, cwd }) => {
14+
expect.assertions(3)
15+
16+
await writeFile('.lintstagedrc.json', JSON.stringify(oxfmtListDifferent))
17+
18+
// Stage pretty file
19+
await writeFile('ita.js', prettyJS)
20+
await execGit(['add', '--intent-to-add', 'ita.js'])
21+
22+
// Run lint-staged with `oxfmt --list-different` and commit pretty file
23+
try {
24+
await gitCommit()
25+
expect.fail('Not reached')
26+
} catch (error) {
27+
expect(error.message).toMatch(
28+
`lint-staged refused to run with files staged with "--intent-to-add":`
29+
)
30+
expect(error.message).toMatch(normalizePath(path.join(cwd, 'ita.js')))
31+
}
32+
33+
// Did not commit
34+
expect(await execGit(['rev-list', '--count', 'HEAD'])).toEqual('1')
35+
})
36+
)
37+
})

‎test/unit/getStagedFiles.spec.js‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('getStagedFiles', () => {
3939
])
4040

4141
expect(execGit).toHaveBeenCalledExactlyOnceWith(
42-
['diff', '--diff-filter=ACMR', '--staged', '--raw', '-z'],
42+
['diff', '--diff-filter=ACMR', '--staged', '--ita-visible-in-index', '--raw', '-z'],
4343
{ cwd: '/' }
4444
)
4545
})
@@ -61,7 +61,7 @@ describe('getStagedFiles', () => {
6161
])
6262

6363
expect(execGit).toHaveBeenCalledExactlyOnceWith(
64-
['diff', '--diff-filter=ACMR', '--staged', '--raw', '-z'],
64+
['diff', '--diff-filter=ACMR', '--staged', '--ita-visible-in-index', '--raw', '-z'],
6565
{ cwd: '/' }
6666
)
6767
})
@@ -122,7 +122,7 @@ describe('getStagedFiles', () => {
122122
])
123123

124124
expect(execGit).toHaveBeenCalledExactlyOnceWith(
125-
['diff', '--diff-filter=ACMR', 'main...my-branch', '--raw', '-z'],
125+
['diff', '--diff-filter=ACMR', 'main...my-branch', '--ita-visible-in-index', '--raw', '-z'],
126126
{ cwd: '/' }
127127
)
128128
})
@@ -145,7 +145,7 @@ describe('getStagedFiles', () => {
145145
])
146146

147147
expect(execGit).toHaveBeenCalledExactlyOnceWith(
148-
['diff', '--diff-filter=ACMR', 'main', 'my-branch', '--raw', '-z'],
148+
['diff', '--diff-filter=ACMR', 'main', 'my-branch', '--ita-visible-in-index', '--raw', '-z'],
149149
{ cwd: '/' }
150150
)
151151
})
@@ -168,7 +168,7 @@ describe('getStagedFiles', () => {
168168
])
169169

170170
expect(execGit).toHaveBeenCalledExactlyOnceWith(
171-
['diff', '--diff-filter=ACDMRTUXB', '--staged', '--raw', '-z'],
171+
['diff', '--diff-filter=ACDMRTUXB', '--staged', '--ita-visible-in-index', '--raw', '-z'],
172172
{ cwd: '/' }
173173
)
174174
})

0 commit comments

Comments
 (0)