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 CHANGED
@@ -1,4 +1,4 @@
1
- # πŸš«πŸ’© lint-staged [![Build Status for Linux](https://travis-ci.org/okonet/lint-staged.svg?branch=master)](https://travis-ci.org/okonet/lint-staged) [![Build Status for Windows](https://ci.appveyor.com/api/projects/status/github/okonet/lint-staged?branch=master&svg=true)](https://ci.appveyor.com/project/okonet/lint-staged) [![npm version](https://badge.fury.io/js/lint-staged.svg)](https://badge.fury.io/js/lint-staged) [![Codecov](https://codecov.io/gh/okonet/lint-staged/branch/master/graph/badge.svg)](https://codecov.io/gh/okonet/lint-staged)
1
+ # πŸš«πŸ’© lint-staged ![GitHub Actions](https://github.com/okonet/lint-staged/workflows/CI/badge.svg) [![Build Status for Windows](https://ci.appveyor.com/api/projects/status/github/okonet/lint-staged?branch=master&svg=true)](https://ci.appveyor.com/project/okonet/lint-staged) [![npm version](https://badge.fury.io/js/lint-staged.svg)](https://badge.fury.io/js/lint-staged) [![Codecov](https://codecov.io/gh/okonet/lint-staged/branch/master/graph/badge.svg)](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.gitDir
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, gitDir, maxArgLength = null, relative = false }) {
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 => normalize(relative ? file : path.resolve(gitDir, 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`
@@ -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, stagedFileChunks }) {
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.stagedFileChunks = stagedFileChunks
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, stash) {
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 (!stash) return
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 entire original state, including unstaged and untracked changes
197
- await this.execGit(['stash', 'save', '--include-untracked', STASH])
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
- await Promise.all(
239
- // stagedFileChunks includes staged files that lint-staged originally detected.
240
- // Add only these files so any 3rd-party edits to other files won't be included in the commit.
241
- this.stagedFileChunks.map(stagedFiles => this.execGit(['add', '--', ...stagedFiles]))
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
- if (this.partiallyStagedFiles) await unlink(PATCH_UNSTAGED)
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({ files, gitDir, maxArgLength, relative })
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
- if (subTasks.some(subTask => subTask.command === 'git add')) {
141
- hasDeprecatedGitAdd = true
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, stagedFileChunks })
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, stash)
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 => stash && shouldSkipApplyModifications(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
- stash &&
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: () => stash,
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 (error.context.emptyGitRepo) {
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 pop stash@{0}\n`)
268
+ > git stash apply --index stash@{0}\n`)
260
269
  }
261
270
  }
262
271
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "10.1.1",
3
+ "version": "10.1.5",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/okonet/lint-staged",