lint-staged 10.1.1 β 10.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/chunkFiles.js +5 -3
- package/lib/gitWorkflow.js +25 -15
- package/lib/runAll.js +31 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# π«π© lint-staged
|
|
1
|
+
# π«π© lint-staged  [](https://ci.appveyor.com/project/okonet/lint-staged) [](https://badge.fury.io/js/lint-staged) [](https://codecov.io/gh/okonet/lint-staged)
|
|
2
2
|
|
|
3
3
|
Run linters against staged git files and don't let :poop: slip into your code base!
|
|
4
4
|
|
package/lib/chunkFiles.js
CHANGED
|
@@ -27,18 +27,20 @@ function chunkArray(arr, chunkCount) {
|
|
|
27
27
|
* Chunk files into sub-arrays based on the length of the resulting argument string
|
|
28
28
|
* @param {Object} opts
|
|
29
29
|
* @param {Array<String>} opts.files
|
|
30
|
-
* @param {String} opts.
|
|
30
|
+
* @param {String} [opts.baseDir] The optional base directory to resolve relative paths.
|
|
31
31
|
* @param {number} [opts.maxArgLength] the maximum argument string length
|
|
32
32
|
* @param {Boolean} [opts.relative] whether files are relative to `gitDir` or should be resolved as absolute
|
|
33
33
|
* @returns {Array<Array<String>>}
|
|
34
34
|
*/
|
|
35
|
-
module.exports = function chunkFiles({ files,
|
|
35
|
+
module.exports = function chunkFiles({ files, baseDir, maxArgLength = null, relative = false }) {
|
|
36
36
|
if (!maxArgLength) {
|
|
37
37
|
debug('Skip chunking files because of undefined maxArgLength')
|
|
38
38
|
return [files]
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const normalizedFiles = files.map(file =>
|
|
41
|
+
const normalizedFiles = files.map(file =>
|
|
42
|
+
normalize(relative || !baseDir ? file : path.resolve(baseDir, file))
|
|
43
|
+
)
|
|
42
44
|
const fileListLength = normalizedFiles.join(' ').length
|
|
43
45
|
debug(
|
|
44
46
|
`Resolved an argument string length of ${fileListLength} characters from ${normalizedFiles.length} files`
|
package/lib/gitWorkflow.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const debug = require('debug')('lint-staged:git')
|
|
4
4
|
const path = require('path')
|
|
5
5
|
|
|
6
|
+
const chunkFiles = require('./chunkFiles')
|
|
6
7
|
const execGit = require('./execGit')
|
|
7
8
|
const { readFile, unlink, writeFile } = require('./file')
|
|
8
9
|
|
|
@@ -52,14 +53,15 @@ const handleError = (error, ctx) => {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
class GitWorkflow {
|
|
55
|
-
constructor({ allowEmpty, gitConfigDir, gitDir,
|
|
56
|
+
constructor({ allowEmpty, gitConfigDir, gitDir, matchedFiles, maxArgLength }) {
|
|
56
57
|
this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: gitDir })
|
|
57
58
|
this.deletedFiles = []
|
|
58
59
|
this.gitConfigDir = gitConfigDir
|
|
59
60
|
this.gitDir = gitDir
|
|
60
61
|
this.unstagedDiff = null
|
|
61
62
|
this.allowEmpty = allowEmpty
|
|
62
|
-
this.
|
|
63
|
+
this.matchedFiles = matchedFiles
|
|
64
|
+
this.maxArgLength = maxArgLength
|
|
63
65
|
|
|
64
66
|
/**
|
|
65
67
|
* These three files hold state about an ongoing git merge
|
|
@@ -164,7 +166,7 @@ class GitWorkflow {
|
|
|
164
166
|
/**
|
|
165
167
|
* Create a diff of partially staged files and backup stash if enabled.
|
|
166
168
|
*/
|
|
167
|
-
async prepare(ctx,
|
|
169
|
+
async prepare(ctx, shouldBackup) {
|
|
168
170
|
try {
|
|
169
171
|
debug('Backing up original state...')
|
|
170
172
|
|
|
@@ -182,7 +184,7 @@ class GitWorkflow {
|
|
|
182
184
|
/**
|
|
183
185
|
* If backup stash should be skipped, no need to continue
|
|
184
186
|
*/
|
|
185
|
-
if (!
|
|
187
|
+
if (!shouldBackup) return
|
|
186
188
|
|
|
187
189
|
// Get a list of unstaged deleted files, because certain bugs might cause them to reappear:
|
|
188
190
|
// - in git versions =< 2.13.0 the `--keep-index` flag resurrects deleted files
|
|
@@ -193,8 +195,8 @@ class GitWorkflow {
|
|
|
193
195
|
// Manually check and backup if necessary
|
|
194
196
|
await this.backupMergeStatus()
|
|
195
197
|
|
|
196
|
-
// Save stash of
|
|
197
|
-
await this.execGit(['stash', 'save',
|
|
198
|
+
// Save stash of original state
|
|
199
|
+
await this.execGit(['stash', 'save', STASH])
|
|
198
200
|
await this.execGit(['stash', 'apply', '--quiet', '--index', await this.getBackupStash()])
|
|
199
201
|
|
|
200
202
|
// Restore meta information about ongoing git merge, cleared by `git stash`
|
|
@@ -205,9 +207,6 @@ class GitWorkflow {
|
|
|
205
207
|
|
|
206
208
|
debug('Done backing up original state!')
|
|
207
209
|
} catch (error) {
|
|
208
|
-
if (error.message && error.message.includes('You do not have the initial commit yet')) {
|
|
209
|
-
ctx.emptyGitRepo = true
|
|
210
|
-
}
|
|
211
210
|
handleError(error, ctx)
|
|
212
211
|
}
|
|
213
212
|
}
|
|
@@ -235,11 +234,22 @@ class GitWorkflow {
|
|
|
235
234
|
*/
|
|
236
235
|
async applyModifications(ctx) {
|
|
237
236
|
debug('Adding task modifications to index...')
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
237
|
+
// `matchedFiles` includes staged files that lint-staged originally detected and matched against a task.
|
|
238
|
+
// Add only these files so any 3rd-party edits to other files won't be included in the commit.
|
|
239
|
+
const files = Array.from(this.matchedFiles)
|
|
240
|
+
// Chunk files for better Windows compatibility
|
|
241
|
+
const matchedFileChunks = chunkFiles({
|
|
242
|
+
baseDir: this.gitDir,
|
|
243
|
+
files,
|
|
244
|
+
maxArgLength: this.maxArgLength
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// These additions per chunk are run "serially" to prevent race conditions.
|
|
248
|
+
// Git add creates a lockfile in the repo causing concurrent operations to fail.
|
|
249
|
+
for (const files of matchedFileChunks) {
|
|
250
|
+
await this.execGit(['add', '--', ...files])
|
|
251
|
+
}
|
|
252
|
+
|
|
243
253
|
debug('Done adding task modifications to index!')
|
|
244
254
|
|
|
245
255
|
const stagedFilesAfterAdd = await this.execGit(['diff', '--name-only', '--cached'])
|
|
@@ -296,7 +306,7 @@ class GitWorkflow {
|
|
|
296
306
|
await Promise.all(this.deletedFiles.map(file => unlink(file)))
|
|
297
307
|
|
|
298
308
|
// Clean out patch
|
|
299
|
-
|
|
309
|
+
await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))
|
|
300
310
|
|
|
301
311
|
debug('Done restoring original state!')
|
|
302
312
|
} catch (error) {
|
package/lib/runAll.js
CHANGED
|
@@ -7,6 +7,7 @@ const Listr = require('listr')
|
|
|
7
7
|
const symbols = require('log-symbols')
|
|
8
8
|
|
|
9
9
|
const chunkFiles = require('./chunkFiles')
|
|
10
|
+
const execGit = require('./execGit')
|
|
10
11
|
const generateTasks = require('./generateTasks')
|
|
11
12
|
const getStagedFiles = require('./getStagedFiles')
|
|
12
13
|
const GitWorkflow = require('./gitWorkflow')
|
|
@@ -91,15 +92,22 @@ const runAll = async (
|
|
|
91
92
|
) => {
|
|
92
93
|
debugLog('Running all linter scripts')
|
|
93
94
|
|
|
94
|
-
if (!stash) {
|
|
95
|
-
logger.warn(
|
|
96
|
-
`${symbols.warning} ${chalk.yellow('Skipping backup because `--no-stash` was used.')}`
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
95
|
const { gitDir, gitConfigDir } = await resolveGitRepo(cwd)
|
|
101
96
|
if (!gitDir) throw new Error('Current directory is not a git directory!')
|
|
102
97
|
|
|
98
|
+
// Test whether we have any commits or not.
|
|
99
|
+
// Stashing must be disabled with no initial commit.
|
|
100
|
+
const hasInitialCommit = await execGit(['log', '-1'], { cwd: gitDir })
|
|
101
|
+
.then(() => true)
|
|
102
|
+
.catch(() => false)
|
|
103
|
+
|
|
104
|
+
// Lint-staged should create a backup stash only when there's an initial commit
|
|
105
|
+
const shouldBackup = hasInitialCommit && stash
|
|
106
|
+
if (!shouldBackup) {
|
|
107
|
+
const reason = hasInitialCommit ? '`--no-stash` was used' : 'thereβs no initial commit yet'
|
|
108
|
+
logger.warn(`${symbols.warning} ${chalk.yellow(`Skipping backup because ${reason}.\n`)}`)
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
const files = await getStagedFiles({ cwd: gitDir })
|
|
104
112
|
if (!files) throw new Error('Unable to get staged files!')
|
|
105
113
|
debugLog('Loaded list of staged files in git:\n%O', files)
|
|
@@ -109,7 +117,7 @@ const runAll = async (
|
|
|
109
117
|
return logger.log(`${symbols.info} No staged files found.`)
|
|
110
118
|
}
|
|
111
119
|
|
|
112
|
-
const stagedFileChunks = chunkFiles({
|
|
120
|
+
const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })
|
|
113
121
|
const chunkCount = stagedFileChunks.length
|
|
114
122
|
if (chunkCount > 1) debugLog(`Chunked staged files into ${chunkCount} part`, chunkCount)
|
|
115
123
|
|
|
@@ -125,6 +133,9 @@ const runAll = async (
|
|
|
125
133
|
|
|
126
134
|
const listrTasks = []
|
|
127
135
|
|
|
136
|
+
// Set of all staged files that matched a task glob. Values in a set are unique.
|
|
137
|
+
const matchedFiles = new Set()
|
|
138
|
+
|
|
128
139
|
for (const [index, files] of stagedFileChunks.entries()) {
|
|
129
140
|
const chunkTasks = generateTasks({ config, cwd, gitDir, files, relative })
|
|
130
141
|
const chunkListrTasks = []
|
|
@@ -137,9 +148,12 @@ const runAll = async (
|
|
|
137
148
|
shell
|
|
138
149
|
})
|
|
139
150
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
// Add files from task to match set
|
|
152
|
+
task.fileList.forEach(file => {
|
|
153
|
+
matchedFiles.add(file)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
hasDeprecatedGitAdd = subTasks.some(subTask => subTask.command === 'git add')
|
|
143
157
|
|
|
144
158
|
chunkListrTasks.push({
|
|
145
159
|
title: `Running tasks for ${task.pattern}`,
|
|
@@ -190,13 +204,13 @@ const runAll = async (
|
|
|
190
204
|
return 'No tasks to run.'
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
const git = new GitWorkflow({ allowEmpty, gitConfigDir, gitDir,
|
|
207
|
+
const git = new GitWorkflow({ allowEmpty, gitConfigDir, gitDir, matchedFiles, maxArgLength })
|
|
194
208
|
|
|
195
209
|
const runner = new Listr(
|
|
196
210
|
[
|
|
197
211
|
{
|
|
198
212
|
title: 'Preparing...',
|
|
199
|
-
task: ctx => git.prepare(ctx,
|
|
213
|
+
task: ctx => git.prepare(ctx, shouldBackup)
|
|
200
214
|
},
|
|
201
215
|
{
|
|
202
216
|
title: 'Hiding unstaged changes to partially staged files...',
|
|
@@ -208,7 +222,7 @@ const runAll = async (
|
|
|
208
222
|
title: 'Applying modifications...',
|
|
209
223
|
task: ctx => git.applyModifications(ctx),
|
|
210
224
|
// Always apply back unstaged modifications when skipping backup
|
|
211
|
-
skip: ctx =>
|
|
225
|
+
skip: ctx => shouldBackup && shouldSkipApplyModifications(ctx)
|
|
212
226
|
},
|
|
213
227
|
{
|
|
214
228
|
title: 'Restoring unstaged changes to partially staged files...',
|
|
@@ -220,14 +234,14 @@ const runAll = async (
|
|
|
220
234
|
title: 'Reverting to original state because of errors...',
|
|
221
235
|
task: ctx => git.restoreOriginalState(ctx),
|
|
222
236
|
enabled: ctx =>
|
|
223
|
-
|
|
237
|
+
shouldBackup &&
|
|
224
238
|
(ctx.taskError || ctx.gitApplyEmptyCommitError || ctx.gitRestoreUnstagedChangesError),
|
|
225
239
|
skip: shouldSkipRevert
|
|
226
240
|
},
|
|
227
241
|
{
|
|
228
242
|
title: 'Cleaning up...',
|
|
229
243
|
task: ctx => git.cleanup(ctx),
|
|
230
|
-
enabled: () =>
|
|
244
|
+
enabled: () => shouldBackup,
|
|
231
245
|
skip: shouldSkipCleanup
|
|
232
246
|
}
|
|
233
247
|
],
|
|
@@ -245,18 +259,13 @@ const runAll = async (
|
|
|
245
259
|
} else if (error.context.gitError && !error.context.gitGetBackupStashError) {
|
|
246
260
|
logger.error(`\n ${symbols.error} ${chalk.red(`lint-staged failed due to a git error.`)}`)
|
|
247
261
|
|
|
248
|
-
if (
|
|
249
|
-
logger.error(
|
|
250
|
-
`\n The initial commit is needed for lint-staged to work.
|
|
251
|
-
Please use the --no-verify flag to skip running lint-staged.`
|
|
252
|
-
)
|
|
253
|
-
} else if (stash) {
|
|
262
|
+
if (shouldBackup) {
|
|
254
263
|
// No sense to show this if the backup stash itself is missing.
|
|
255
264
|
logger.error(` Any lost modifications can be restored from a git stash:
|
|
256
265
|
|
|
257
266
|
> git stash list
|
|
258
267
|
stash@{0}: On master: automatic lint-staged backup
|
|
259
|
-
> git stash
|
|
268
|
+
> git stash apply --index stash@{0}\n`)
|
|
260
269
|
}
|
|
261
270
|
}
|
|
262
271
|
|