lint-staged 11.2.4 → 11.3.0-beta.2

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
@@ -65,8 +65,7 @@ Options:
65
65
  (default: false)
66
66
  -c, --config [path] path to configuration file, or - to read from stdin
67
67
  -d, --debug print additional debug information (default: false)
68
- --no-stash disable the backup stash, and do not revert in case of
69
- errors
68
+ --no-reset do not reset changes in case of errors
70
69
  -p, --concurrent <parallel tasks> the number of tasks to run concurrently, or false to run
71
70
  tasks serially (default: true)
72
71
  -q, --quiet disable lint-staged’s own console output (default: false)
@@ -87,7 +86,7 @@ Options:
87
86
  - `false`: Run all tasks serially
88
87
  - `true` (default) : _Infinite_ concurrency. Runs as many tasks in parallel as possible.
89
88
  - `{number}`: Run the specified number of tasks in parallel, where `1` is equivalent to `false`.
90
- - **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit.
89
+ - **`--no-reset`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit.
91
90
  - **`--quiet`**: Supress all CLI output, except from tasks.
92
91
  - **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
93
92
  - **`--shell`**: By default linter commands will be parsed for speed and security. This has the side-effect that regular shell scripts might not work as expected. You can skip parsing of commands with this option. To use a specific shell, use a path like `--shell "/bin/bash"`.
@@ -105,7 +104,7 @@ Starting with v3.1 you can now use different ways of configuring lint-staged:
105
104
  - `lint-staged.config.js`, `.lintstagedrc.js`, or `.lintstagedrc.cjs` file in JS format
106
105
  - Pass a configuration file using the `--config` or `-c` flag
107
106
 
108
- See [lilconfig](https://github.com/antonk52/lilconfig) for more details on what formats are supported.
107
+ See [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) for more details on what formats are supported.
109
108
 
110
109
  Configuration should be an object where each value is a command to run and its key is a glob pattern to use for this command. This package uses [micromatch](https://github.com/micromatch/micromatch) for glob patterns.
111
110
 
@@ -565,8 +564,8 @@ const success = await lintStaged({
565
564
  maxArgLength: null,
566
565
  quiet: false,
567
566
  relative: false,
567
+ reset: true,
568
568
  shell: false
569
- stash: true,
570
569
  verbose: false
571
570
  })
572
571
  ```
@@ -583,8 +582,8 @@ const success = await lintStaged({
583
582
  maxArgLength: null,
584
583
  quiet: false,
585
584
  relative: false,
585
+ reset: true,
586
586
  shell: false,
587
- stash: true,
588
587
  verbose: false,
589
588
  })
590
589
  ```
@@ -22,19 +22,22 @@ require('please-upgrade-node')(
22
22
  })
23
23
  )
24
24
 
25
- const cmdline = require('commander')
25
+ const { Command, Option } = require('commander')
26
26
  const debugLib = require('debug')
27
27
  const lintStaged = require('../lib')
28
28
  const { CONFIG_STDIN_ERROR } = require('../lib/messages')
29
29
 
30
30
  const debug = debugLib('lint-staged:bin')
31
31
 
32
- cmdline
33
- .version(pkg.version)
32
+ const program = new Command('lint-staged')
33
+
34
+ program.version(pkg.version)
35
+
36
+ program
34
37
  .option('--allow-empty', 'allow empty commits when tasks revert all staged changes', false)
35
38
  .option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
36
39
  .option('-d, --debug', 'print additional debug information', false)
37
- .option('--no-stash', 'disable the backup stash, and do not revert in case of errors', false)
40
+ .option('--no-reset', 'do not reset changes in case of errors', false)
38
41
  .option(
39
42
  '-p, --concurrent <parallel tasks>',
40
43
  'the number of tasks to run concurrently, or false to run tasks serially',
@@ -48,9 +51,13 @@ cmdline
48
51
  'show task output even when tasks succeed; by default only failed output is shown',
49
52
  false
50
53
  )
51
- .parse(process.argv)
52
54
 
53
- if (cmdline.debug) {
55
+ // Added for backwards-compatibility
56
+ program.addOption(new Option('--no-stash').hideHelp())
57
+
58
+ program.parse(process.argv)
59
+
60
+ if (program.debug) {
54
61
  debugLib.enable('lint-staged*')
55
62
  }
56
63
 
@@ -74,19 +81,20 @@ const getMaxArgLength = () => {
74
81
  }
75
82
  }
76
83
 
77
- const cmdlineOptions = cmdline.opts()
84
+ const programOpts = program.opts()
78
85
 
79
86
  const options = {
80
- allowEmpty: !!cmdlineOptions.allowEmpty,
81
- concurrent: JSON.parse(cmdlineOptions.concurrent),
82
- configPath: cmdlineOptions.config,
83
- debug: !!cmdlineOptions.debug,
87
+ allowEmpty: !!programOpts.allowEmpty,
88
+ concurrent: JSON.parse(programOpts.concurrent),
89
+ configPath: programOpts.config,
90
+ debug: !!programOpts.debug,
84
91
  maxArgLength: getMaxArgLength() / 2,
85
- stash: !!cmdlineOptions.stash, // commander inverts `no-<x>` flags to `!x`
86
- quiet: !!cmdlineOptions.quiet,
87
- relative: !!cmdlineOptions.relative,
88
- shell: cmdlineOptions.shell /* Either a boolean or a string pointing to the shell */,
89
- verbose: !!cmdlineOptions.verbose,
92
+ quiet: !!programOpts.quiet,
93
+ relative: !!programOpts.relative,
94
+ reset: !!programOpts.reset, // commander inverts `no-<x>` flags to `!x`
95
+ shell: programOpts.shell /* Either a boolean or a string pointing to the shell */,
96
+ stash: programOpts.stash /** kept for backwards-compatibility */,
97
+ verbose: !!programOpts.verbose,
90
98
  }
91
99
 
92
100
  debug('Options parsed from command-line:', options)
@@ -1,19 +1,21 @@
1
1
  'use strict'
2
2
 
3
3
  const debug = require('debug')('lint-staged:git')
4
+ const fs = require('fs')
4
5
  const path = require('path')
6
+ const { promisify } = require('util')
5
7
 
6
8
  const execGit = require('./execGit')
7
- const { readFile, unlink, writeFile } = require('./file')
8
9
  const {
9
10
  GitError,
10
- RestoreOriginalStateError,
11
11
  ApplyEmptyCommitError,
12
- GetBackupStashError,
13
12
  HideUnstagedChangesError,
14
- RestoreMergeStatusError,
13
+ RestoreOriginalStateError,
15
14
  RestoreUnstagedChangesError,
16
15
  } = require('./symbols')
16
+ const unlink = require('./unlink')
17
+
18
+ const fsReadFile = promisify(fs.readFile)
17
19
 
18
20
  const MERGE_HEAD = 'MERGE_HEAD'
19
21
  const MERGE_MODE = 'MERGE_MODE'
@@ -41,9 +43,8 @@ const processRenames = (files, includeRenameFrom = true) =>
41
43
  return flattened
42
44
  }, [])
43
45
 
44
- const STASH = 'lint-staged automatic backup'
45
-
46
46
  const PATCH_UNSTAGED = 'lint-staged_unstaged.patch'
47
+ const PATCH_PARTIAL = 'lint-staged_partial.patch'
47
48
 
48
49
  const GIT_DIFF_ARGS = [
49
50
  '--binary', // support binary files
@@ -90,69 +91,6 @@ class GitWorkflow {
90
91
  return path.resolve(this.gitConfigDir, `./${filename}`)
91
92
  }
92
93
 
93
- /**
94
- * Get name of backup stash
95
- */
96
- async getBackupStash(ctx) {
97
- const stashes = await this.execGit(['stash', 'list'])
98
- const index = stashes.split('\n').findIndex((line) => line.includes(STASH))
99
- if (index === -1) {
100
- ctx.errors.add(GetBackupStashError)
101
- throw new Error('lint-staged automatic backup is missing!')
102
- }
103
- return `refs/stash@{${index}}`
104
- }
105
-
106
- /**
107
- * Get a list of unstaged deleted files
108
- */
109
- async getDeletedFiles() {
110
- debug('Getting deleted files...')
111
- const lsFiles = await this.execGit(['ls-files', '--deleted'])
112
- const deletedFiles = lsFiles
113
- .split('\n')
114
- .filter(Boolean)
115
- .map((file) => path.resolve(this.gitDir, file))
116
- debug('Found deleted files:', deletedFiles)
117
- return deletedFiles
118
- }
119
-
120
- /**
121
- * Save meta information about ongoing git merge
122
- */
123
- async backupMergeStatus() {
124
- debug('Backing up merge state...')
125
- await Promise.all([
126
- readFile(this.mergeHeadFilename).then((buffer) => (this.mergeHeadBuffer = buffer)),
127
- readFile(this.mergeModeFilename).then((buffer) => (this.mergeModeBuffer = buffer)),
128
- readFile(this.mergeMsgFilename).then((buffer) => (this.mergeMsgBuffer = buffer)),
129
- ])
130
- debug('Done backing up merge state!')
131
- }
132
-
133
- /**
134
- * Restore meta information about ongoing git merge
135
- */
136
- async restoreMergeStatus(ctx) {
137
- debug('Restoring merge state...')
138
- try {
139
- await Promise.all([
140
- this.mergeHeadBuffer && writeFile(this.mergeHeadFilename, this.mergeHeadBuffer),
141
- this.mergeModeBuffer && writeFile(this.mergeModeFilename, this.mergeModeBuffer),
142
- this.mergeMsgBuffer && writeFile(this.mergeMsgFilename, this.mergeMsgBuffer),
143
- ])
144
- debug('Done restoring merge state!')
145
- } catch (error) {
146
- debug('Failed restoring merge state with error:')
147
- debug(error)
148
- handleError(
149
- new Error('Merge state could not be restored due to an error!'),
150
- ctx,
151
- RestoreMergeStatusError
152
- )
153
- }
154
- }
155
-
156
94
  /**
157
95
  * Get a list of all files with both staged and unstaged modifications.
158
96
  * Renames have special treatment, since the single status line includes
@@ -184,44 +122,28 @@ class GitWorkflow {
184
122
  }
185
123
 
186
124
  /**
187
- * Create a diff of partially staged files and backup stash if enabled.
125
+ * Create a diff of partially staged files.
188
126
  */
189
127
  async prepare(ctx) {
190
128
  try {
191
129
  debug('Backing up original state...')
192
130
 
131
+ const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
132
+ await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch])
133
+
193
134
  // Get a list of files with bot staged and unstaged changes.
194
135
  // Unstaged changes to these files should be hidden before the tasks run.
195
136
  this.partiallyStagedFiles = await this.getPartiallyStagedFiles()
196
137
 
197
138
  if (this.partiallyStagedFiles) {
198
139
  ctx.hasPartiallyStagedFiles = true
199
- const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
140
+ const partialPatch = this.getHiddenFilepath(PATCH_PARTIAL)
200
141
  const files = processRenames(this.partiallyStagedFiles)
201
- await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch, '--', ...files])
142
+ await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', partialPatch, '--', ...files])
202
143
  } else {
203
144
  ctx.hasPartiallyStagedFiles = false
204
145
  }
205
146
 
206
- /**
207
- * If backup stash should be skipped, no need to continue
208
- */
209
- if (!ctx.shouldBackup) return
210
-
211
- // When backup is enabled, the revert will clear ongoing merge status.
212
- await this.backupMergeStatus()
213
-
214
- // Get a list of unstaged deleted files, because certain bugs might cause them to reappear:
215
- // - in git versions =< 2.13.0 the `git stash --keep-index` option resurrects deleted files
216
- // - git stash can't infer RD or MD states correctly, and will lose the deletion
217
- this.deletedFiles = await this.getDeletedFiles()
218
-
219
- // Save stash of all staged files.
220
- // The `stash create` command creates a dangling commit without removing any files,
221
- // and `stash store` saves it as an actual stash.
222
- const hash = await this.execGit(['stash', 'create'])
223
- await this.execGit(['stash', 'store', '--quiet', '--message', STASH, hash])
224
-
225
147
  debug('Done backing up original state!')
226
148
  } catch (error) {
227
149
  handleError(error, ctx)
@@ -244,13 +166,19 @@ class GitWorkflow {
244
166
  }
245
167
  }
246
168
 
247
- /**
248
- * Applies back task modifications, and unstaged changes hidden in the stash.
249
- * In case of a merge-conflict retry with 3-way merge.
250
- */
169
+ /** Add all task modifications to index for files that were staged before running. */
251
170
  async applyModifications(ctx) {
252
171
  debug('Adding task modifications to index...')
253
172
 
173
+ if (ctx.hasInitialCommit) {
174
+ const stagedFilesAfterAdd = await this.execGit(['diff', 'HEAD'])
175
+ if (!stagedFilesAfterAdd && !this.allowEmpty) {
176
+ // Tasks reverted all staged changes and the commit would be empty
177
+ // Throw error to stop commit unless `--allow-empty` was used
178
+ handleError(new Error('Prevented an empty git commit!'), ctx, ApplyEmptyCommitError)
179
+ }
180
+ }
181
+
254
182
  // `matchedFileChunks` includes staged files that lint-staged originally detected and matched against a task.
255
183
  // Add only these files so any 3rd-party edits to other files won't be included in the commit.
256
184
  // These additions per chunk are run "serially" to prevent race conditions.
@@ -260,12 +188,25 @@ class GitWorkflow {
260
188
  }
261
189
 
262
190
  debug('Done adding task modifications to index!')
191
+ }
192
+
193
+ /**
194
+ * Restore original HEAD state in case of errors
195
+ */
196
+ async restoreOriginalState(ctx) {
197
+ try {
198
+ debug('Restoring original state...')
199
+ await this.execGit(['checkout', '--force', '--', '.'])
263
200
 
264
- const stagedFilesAfterAdd = await this.execGit(['diff', '--name-only', '--cached'])
265
- if (!stagedFilesAfterAdd && !this.allowEmpty) {
266
- // Tasks reverted all staged changes and the commit would be empty
267
- // Throw error to stop commit unless `--allow-empty` was used
268
- handleError(new Error('Prevented an empty git commit!'), ctx, ApplyEmptyCommitError)
201
+ const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
202
+ const hasContents = !!(await fsReadFile(unstagedPatch)).toString()
203
+ if (hasContents) {
204
+ await this.execGit(['apply', ...GIT_APPLY_ARGS, unstagedPatch])
205
+ }
206
+
207
+ debug('Done restoring original state!')
208
+ } catch (error) {
209
+ handleError(error, ctx, RestoreOriginalStateError)
269
210
  }
270
211
  }
271
212
 
@@ -274,18 +215,18 @@ class GitWorkflow {
274
215
  * this is probably because of conflicts between new task modifications.
275
216
  * 3-way merge usually fixes this, and in case it doesn't we should just give up and throw.
276
217
  */
277
- async restoreUnstagedChanges(ctx) {
218
+ async restorePartialChanges(ctx) {
278
219
  debug('Restoring unstaged changes...')
279
- const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
220
+ const partialPatch = this.getHiddenFilepath(PATCH_PARTIAL)
280
221
  try {
281
- await this.execGit(['apply', ...GIT_APPLY_ARGS, unstagedPatch])
222
+ await this.execGit(['apply', ...GIT_APPLY_ARGS, partialPatch])
282
223
  } catch (applyError) {
283
224
  debug('Error while restoring changes:')
284
225
  debug(applyError)
285
226
  debug('Retrying with 3-way merge')
286
227
  try {
287
228
  // Retry with a 3-way merge if normal apply fails
288
- await this.execGit(['apply', ...GIT_APPLY_ARGS, '--3way', unstagedPatch])
229
+ await this.execGit(['apply', ...GIT_APPLY_ARGS, '--3way', partialPatch])
289
230
  } catch (threeWayApplyError) {
290
231
  debug('Error while restoring unstaged changes using 3-way merge:')
291
232
  debug(threeWayApplyError)
@@ -299,40 +240,14 @@ class GitWorkflow {
299
240
  }
300
241
 
301
242
  /**
302
- * Restore original HEAD state in case of errors
303
- */
304
- async restoreOriginalState(ctx) {
305
- try {
306
- debug('Restoring original state...')
307
- await this.execGit(['reset', '--hard', 'HEAD'])
308
- await this.execGit(['stash', 'apply', '--quiet', '--index', await this.getBackupStash(ctx)])
309
-
310
- // Restore meta information about ongoing git merge
311
- await this.restoreMergeStatus(ctx)
312
-
313
- // If stashing resurrected deleted files, clean them out
314
- await Promise.all(this.deletedFiles.map((file) => unlink(file)))
315
-
316
- // Clean out patch
317
- await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))
318
-
319
- debug('Done restoring original state!')
320
- } catch (error) {
321
- handleError(error, ctx, RestoreOriginalStateError)
322
- }
323
- }
324
-
325
- /**
326
- * Drop the created stashes after everything has run
243
+ * Drop the created diff file after everything has run.
244
+ * Won't throw if the file has already been deleted (theoretically).
327
245
  */
328
- async cleanup(ctx) {
329
- try {
330
- debug('Dropping backup stash...')
331
- await this.execGit(['stash', 'drop', '--quiet', await this.getBackupStash(ctx)])
332
- debug('Done dropping backup stash!')
333
- } catch (error) {
334
- handleError(error, ctx)
335
- }
246
+ async cleanup() {
247
+ debug('Removing temp files...')
248
+ await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))
249
+ await unlink(this.getHiddenFilepath(PATCH_PARTIAL))
250
+ debug('Done removing temp files!')
336
251
  }
337
252
  }
338
253
 
package/lib/index.js CHANGED
@@ -1,19 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { lilconfig } = require('lilconfig')
4
- const { yaml } = require('js-yaml')
3
+ const { cosmiconfig } = require('cosmiconfig')
5
4
  const debugLog = require('debug')('lint-staged')
6
5
  const stringifyObject = require('stringify-object')
7
6
 
8
- const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./messages')
7
+ const { PREVENTED_EMPTY_COMMIT, GIT_ERROR } = require('./messages')
9
8
  const printTaskOutput = require('./printTaskOutput')
10
9
  const runAll = require('./runAll')
11
- const {
12
- ApplyEmptyCommitError,
13
- ConfigNotFoundError,
14
- GetBackupStashError,
15
- GitError,
16
- } = require('./symbols')
10
+ const { ApplyEmptyCommitError, ConfigNotFoundError, GitError } = require('./symbols')
17
11
  const validateConfig = require('./validateConfig')
18
12
  const validateOptions = require('./validateOptions')
19
13
 
@@ -25,10 +19,8 @@ const resolveConfig = (configPath) => {
25
19
  }
26
20
  }
27
21
 
28
- const jsYamlLoad = (filepath, content) => yaml.load(content)
29
-
30
22
  const loadConfig = (configPath) => {
31
- const explorer = lilconfig('lint-staged', {
23
+ const explorer = cosmiconfig('lint-staged', {
32
24
  searchPlaces: [
33
25
  'package.json',
34
26
  '.lintstagedrc',
@@ -40,11 +32,6 @@ const loadConfig = (configPath) => {
40
32
  'lint-staged.config.js',
41
33
  'lint-staged.config.cjs',
42
34
  ],
43
- loaders: {
44
- '.yml': jsYamlLoad,
45
- '.yaml': jsYamlLoad,
46
- noExt: jsYamlLoad,
47
- },
48
35
  })
49
36
 
50
37
  return configPath ? explorer.load(resolveConfig(configPath)) : explorer.search()
@@ -66,8 +53,8 @@ const loadConfig = (configPath) => {
66
53
  * @param {number} [options.maxArgLength] - Maximum argument string length
67
54
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
68
55
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
56
+ * @param {boolean} [options.reset] - Reset changes in case of errors
69
57
  * @param {boolean|string} [options.shell] - Skip parsing of tasks for better shell support
70
- * @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
71
58
  * @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
72
59
  * @param {Logger} [logger]
73
60
  *
@@ -84,15 +71,16 @@ const lintStaged = async (
84
71
  maxArgLength,
85
72
  quiet = false,
86
73
  relative = false,
74
+ reset = true,
87
75
  shell = false,
88
- stash = true,
76
+ stash, // kept for backwards-compatibility
89
77
  verbose = false,
90
78
  } = {},
91
79
  logger = console
92
80
  ) => {
93
- await validateOptions({ shell }, logger)
81
+ await validateOptions({ shell, stash }, logger)
94
82
 
95
- debugLog('Loading config using `lilconfig`')
83
+ debugLog('Loading config using `cosmiconfig`')
96
84
 
97
85
  const resolved = configObject
98
86
  ? { config: configObject, filepath: '(input)' }
@@ -134,8 +122,8 @@ const lintStaged = async (
134
122
  maxArgLength,
135
123
  quiet,
136
124
  relative,
125
+ reset: reset || !!stash,
137
126
  shell,
138
- stash,
139
127
  verbose,
140
128
  },
141
129
  logger
@@ -148,12 +136,8 @@ const lintStaged = async (
148
136
  const { ctx } = runAllError
149
137
  if (ctx.errors.has(ApplyEmptyCommitError)) {
150
138
  logger.warn(PREVENTED_EMPTY_COMMIT)
151
- } else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
139
+ } else if (ctx.errors.has(GitError)) {
152
140
  logger.error(GIT_ERROR)
153
- if (ctx.shouldBackup) {
154
- // No sense to show this if the backup stash itself is missing.
155
- logger.error(RESTORE_STASH_EXAMPLE)
156
- }
157
141
  }
158
142
 
159
143
  printTaskOutput(ctx, logger)
package/lib/messages.js CHANGED
@@ -28,9 +28,9 @@ const NO_STAGED_FILES = `${info} No staged files found.`
28
28
 
29
29
  const NO_TASKS = `${info} No staged files match any configured task.`
30
30
 
31
- const skippingBackup = (hasInitialCommit) => {
32
- const reason = hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet'
33
- return yellow(`${warning} Skipping backup because ${reason}.\n`)
31
+ const skippingReset = (hasInitialCommit) => {
32
+ const reason = hasInitialCommit ? '`--no-reset` was used' : 'there’s no initial commit yet'
33
+ return yellow(`${warning} Changes won't be reset because ${reason}.\n`)
34
34
  }
35
35
 
36
36
  const DEPRECATED_GIT_ADD = yellow(
@@ -38,6 +38,11 @@ const DEPRECATED_GIT_ADD = yellow(
38
38
  `
39
39
  )
40
40
 
41
+ const DEPRECATED_NO_STASH = yellow(
42
+ `${warning} The \`--no-stash\` option has been renamed to \`--no-reset\`.
43
+ `
44
+ )
45
+
41
46
  const TASK_ERROR = 'Skipped because of errors from tasks.'
42
47
 
43
48
  const SKIPPED_GIT_ERROR = 'Skipped because of previous git error.'
@@ -57,19 +62,13 @@ const PREVENTED_EMPTY_COMMIT = `
57
62
  Use the --allow-empty option to continue, or check your task configuration`)}
58
63
  `
59
64
 
60
- const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored from a git stash:
61
-
62
- > git stash list
63
- stash@{0}: automatic lint-staged backup
64
- > git stash apply --index stash@{0}
65
- `
66
-
67
65
  const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.'
68
66
 
69
67
  module.exports = {
70
68
  CONFIG_STDIN_ERROR,
71
69
  configurationError,
72
70
  DEPRECATED_GIT_ADD,
71
+ DEPRECATED_NO_STASH,
73
72
  FAILED_GET_STAGED_FILES,
74
73
  GIT_ERROR,
75
74
  incorrectBraces,
@@ -78,8 +77,7 @@ module.exports = {
78
77
  NO_TASKS,
79
78
  NOT_GIT_REPO,
80
79
  PREVENTED_EMPTY_COMMIT,
81
- RESTORE_STASH_EXAMPLE,
82
80
  SKIPPED_GIT_ERROR,
83
- skippingBackup,
81
+ skippingReset,
84
82
  TASK_ERROR,
85
83
  }
@@ -7,9 +7,9 @@ const path = require('path')
7
7
  const { promisify } = require('util')
8
8
 
9
9
  const execGit = require('./execGit')
10
- const { readFile } = require('./file')
11
10
 
12
11
  const fsLstat = promisify(fs.lstat)
12
+ const fsReadFile = promisify(fs.readFile)
13
13
 
14
14
  /**
15
15
  * Resolve path to the .git directory, with special handling for
@@ -21,7 +21,7 @@ const resolveGitConfigDir = async (gitDir) => {
21
21
  // If .git is a directory, use it
22
22
  if (stats.isDirectory()) return defaultDir
23
23
  // Otherwise .git is a file containing path to real location
24
- const file = (await readFile(defaultDir)).toString()
24
+ const file = (await fsReadFile(defaultDir)).toString()
25
25
  return path.resolve(gitDir, file.replace(/^gitdir: /, '')).trim()
26
26
  }
27
27
 
package/lib/runAll.js CHANGED
@@ -19,7 +19,7 @@ const {
19
19
  NO_STAGED_FILES,
20
20
  NO_TASKS,
21
21
  SKIPPED_GIT_ERROR,
22
- skippingBackup,
22
+ skippingReset,
23
23
  } = require('./messages')
24
24
  const resolveGitRepo = require('./resolveGitRepo')
25
25
  const {
@@ -30,7 +30,7 @@ const {
30
30
  hasPartiallyStagedFiles,
31
31
  restoreOriginalStateEnabled,
32
32
  restoreOriginalStateSkipped,
33
- restoreUnstagedChangesSkipped,
33
+ restorePartialChangesSkipped,
34
34
  } = require('./state')
35
35
  const { GitRepoError, GetStagedFilesError, GitError } = require('./symbols')
36
36
 
@@ -48,8 +48,8 @@ const createError = (ctx) => Object.assign(new Error('lint-staged failed'), { ct
48
48
  * @param {number} [options.maxArgLength] - Maximum argument string length
49
49
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
50
50
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
51
+ * @param {boolean} [options.reset] - Reset changes in case of errors
51
52
  * @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
52
- * @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
53
53
  * @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
54
54
  * @param {Logger} logger
55
55
  * @returns {Promise}
@@ -64,8 +64,8 @@ const runAll = async (
64
64
  maxArgLength,
65
65
  quiet = false,
66
66
  relative = false,
67
+ reset = true,
67
68
  shell = false,
68
- stash = true,
69
69
  verbose = false,
70
70
  },
71
71
  logger = console
@@ -87,10 +87,11 @@ const runAll = async (
87
87
  .then(() => true)
88
88
  .catch(() => false)
89
89
 
90
- // Lint-staged should create a backup stash only when there's an initial commit
91
- ctx.shouldBackup = hasInitialCommit && stash
92
- if (!ctx.shouldBackup) {
93
- logger.warn(skippingBackup(hasInitialCommit))
90
+ // Lint-staged should reset changes when errors happen only when there's an initial commit
91
+ ctx.hasInitialCommit = hasInitialCommit
92
+ ctx.shouldReset = hasInitialCommit && reset
93
+ if (!ctx.shouldReset) {
94
+ logger.warn(skippingReset(hasInitialCommit))
94
95
  }
95
96
 
96
97
  const files = await getStagedFiles({ cwd: gitDir })
@@ -225,20 +226,20 @@ const runAll = async (
225
226
  skip: applyModificationsSkipped,
226
227
  },
227
228
  {
228
- title: 'Restoring unstaged changes to partially staged files...',
229
- task: (ctx) => git.restoreUnstagedChanges(ctx),
230
- enabled: hasPartiallyStagedFiles,
231
- skip: restoreUnstagedChangesSkipped,
232
- },
233
- {
234
- title: 'Reverting to original state because of errors...',
229
+ title: 'Reverting because of errors...',
235
230
  task: (ctx) => git.restoreOriginalState(ctx),
236
231
  enabled: restoreOriginalStateEnabled,
237
232
  skip: restoreOriginalStateSkipped,
238
233
  },
234
+ {
235
+ title: 'Restoring partial changes...',
236
+ task: (ctx) => git.restorePartialChanges(ctx),
237
+ enabled: hasPartiallyStagedFiles,
238
+ skip: restorePartialChangesSkipped,
239
+ },
239
240
  {
240
241
  title: 'Cleaning up...',
241
- task: (ctx) => git.cleanup(ctx),
242
+ task: () => git.cleanup(),
242
243
  enabled: cleanupEnabled,
243
244
  skip: cleanupSkipped,
244
245
  },
package/lib/state.js CHANGED
@@ -4,14 +4,13 @@ const { GIT_ERROR, TASK_ERROR } = require('./messages')
4
4
  const {
5
5
  ApplyEmptyCommitError,
6
6
  TaskError,
7
- RestoreOriginalStateError,
8
7
  GitError,
9
8
  RestoreUnstagedChangesError,
10
9
  } = require('./symbols')
11
10
 
12
11
  const getInitialState = ({ quiet = false } = {}) => ({
13
12
  hasPartiallyStagedFiles: null,
14
- shouldBackup: null,
13
+ shouldReset: null,
15
14
  errors: new Set([]),
16
15
  output: [],
17
16
  quiet,
@@ -21,33 +20,40 @@ const hasPartiallyStagedFiles = (ctx) => ctx.hasPartiallyStagedFiles
21
20
 
22
21
  const applyModificationsSkipped = (ctx) => {
23
22
  // Always apply back unstaged modifications when skipping backup
24
- if (!ctx.shouldBackup) return false
23
+ if (!ctx.shouldReset) return false
24
+
25
25
  // Should be skipped in case of git errors
26
26
  if (ctx.errors.has(GitError)) {
27
27
  return GIT_ERROR
28
28
  }
29
+
29
30
  // Should be skipped when tasks fail
30
31
  if (ctx.errors.has(TaskError)) {
31
32
  return TASK_ERROR
32
33
  }
33
34
  }
34
35
 
35
- const restoreUnstagedChangesSkipped = (ctx) => {
36
+ const restorePartialChangesSkipped = (ctx) => {
37
+ // Should be skipped when entire state has already been restored
38
+ if (restoreOriginalStateEnabled(ctx)) {
39
+ return TASK_ERROR
40
+ }
41
+
36
42
  // Should be skipped in case of git errors
37
43
  if (ctx.errors.has(GitError)) {
38
44
  return GIT_ERROR
39
45
  }
40
- // Should be skipped when tasks fail
41
- if (ctx.errors.has(TaskError)) {
46
+ }
47
+
48
+ const restoreOriginalStateEnabled = (ctx) => {
49
+ if (ctx.shouldReset && ctx.errors.has(TaskError)) {
42
50
  return TASK_ERROR
43
51
  }
44
- }
45
52
 
46
- const restoreOriginalStateEnabled = (ctx) =>
47
- ctx.shouldBackup &&
48
- (ctx.errors.has(TaskError) ||
49
- ctx.errors.has(ApplyEmptyCommitError) ||
50
- ctx.errors.has(RestoreUnstagedChangesError))
53
+ if (ctx.errors.has(ApplyEmptyCommitError)) {
54
+ return true
55
+ }
56
+ }
51
57
 
52
58
  const restoreOriginalStateSkipped = (ctx) => {
53
59
  // Should be skipped in case of unknown git errors
@@ -60,7 +66,7 @@ const restoreOriginalStateSkipped = (ctx) => {
60
66
  }
61
67
  }
62
68
 
63
- const cleanupEnabled = (ctx) => ctx.shouldBackup
69
+ const cleanupEnabled = (ctx) => ctx.shouldReset
64
70
 
65
71
  const cleanupSkipped = (ctx) => {
66
72
  // Should be skipped in case of unknown git errors
@@ -71,17 +77,13 @@ const cleanupSkipped = (ctx) => {
71
77
  ) {
72
78
  return GIT_ERROR
73
79
  }
74
- // Should be skipped when reverting to original state fails
75
- if (ctx.errors.has(RestoreOriginalStateError)) {
76
- return GIT_ERROR
77
- }
78
80
  }
79
81
 
80
82
  module.exports = {
81
83
  getInitialState,
82
84
  hasPartiallyStagedFiles,
83
85
  applyModificationsSkipped,
84
- restoreUnstagedChangesSkipped,
86
+ restorePartialChangesSkipped,
85
87
  restoreOriginalStateEnabled,
86
88
  restoreOriginalStateSkipped,
87
89
  cleanupEnabled,
package/lib/symbols.js CHANGED
@@ -2,28 +2,24 @@
2
2
 
3
3
  const ApplyEmptyCommitError = Symbol('ApplyEmptyCommitError')
4
4
  const ConfigNotFoundError = new Error('Config could not be found')
5
- const GetBackupStashError = Symbol('GetBackupStashError')
6
5
  const GetStagedFilesError = Symbol('GetStagedFilesError')
7
6
  const GitError = Symbol('GitError')
8
7
  const GitRepoError = Symbol('GitRepoError')
9
8
  const HideUnstagedChangesError = Symbol('HideUnstagedChangesError')
10
9
  const InvalidOptionsError = new Error('Invalid Options')
11
10
  const RestoreMergeStatusError = Symbol('RestoreMergeStatusError')
12
- const RestoreOriginalStateError = Symbol('RestoreOriginalStateError')
13
11
  const RestoreUnstagedChangesError = Symbol('RestoreUnstagedChangesError')
14
12
  const TaskError = Symbol('TaskError')
15
13
 
16
14
  module.exports = {
17
15
  ApplyEmptyCommitError,
18
16
  ConfigNotFoundError,
19
- GetBackupStashError,
20
17
  GetStagedFilesError,
21
18
  GitError,
22
19
  GitRepoError,
23
20
  InvalidOptionsError,
24
21
  HideUnstagedChangesError,
25
22
  RestoreMergeStatusError,
26
- RestoreOriginalStateError,
27
23
  RestoreUnstagedChangesError,
28
24
  TaskError,
29
25
  }
package/lib/unlink.js ADDED
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ const debug = require('debug')('lint-staged:file')
4
+ const fs = require('fs')
5
+ const { promisify } = require('util')
6
+
7
+ const fsUnlink = promisify(fs.unlink)
8
+
9
+ /**
10
+ * Remove a file if it exists
11
+ * @param {String} filename
12
+ */
13
+ const unlink = async (filename) => {
14
+ debug('Removing file `%s`', filename)
15
+ try {
16
+ await fsUnlink(filename)
17
+ } catch (error) {
18
+ debug("File `%s` doesn't exist, ignoring...", filename)
19
+ }
20
+ }
21
+
22
+ module.exports = unlink
@@ -1,6 +1,6 @@
1
1
  const { promises: fs, constants } = require('fs')
2
2
 
3
- const { invalidOption } = require('./messages')
3
+ const { DEPRECATED_NO_STASH, invalidOption } = require('./messages')
4
4
  const { InvalidOptionsError } = require('./symbols')
5
5
 
6
6
  const debug = require('debug')('lint-staged:options')
@@ -25,6 +25,10 @@ const validateOptions = async (options = {}, logger) => {
25
25
  }
26
26
  }
27
27
 
28
+ if (typeof options.stash === 'boolean') {
29
+ logger.warn(DEPRECATED_NO_STASH)
30
+ }
31
+
28
32
  debug('Validated options!')
29
33
  }
30
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "11.2.4",
3
+ "version": "11.3.0-beta.2",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/okonet/lint-staged",
@@ -30,11 +30,10 @@
30
30
  "cli-truncate": "2.1.0",
31
31
  "colorette": "^1.4.0",
32
32
  "commander": "^8.2.0",
33
+ "cosmiconfig": "^7.0.1",
33
34
  "debug": "^4.3.2",
34
35
  "enquirer": "^2.3.6",
35
36
  "execa": "^5.1.1",
36
- "js-yaml": "^4.1.0",
37
- "lilconfig": "^2.0.3",
38
37
  "listr2": "^3.12.2",
39
38
  "micromatch": "^4.0.4",
40
39
  "normalize-path": "^3.0.0",
package/lib/file.js DELETED
@@ -1,63 +0,0 @@
1
- 'use strict'
2
-
3
- const debug = require('debug')('lint-staged:file')
4
- const fs = require('fs')
5
- const { promisify } = require('util')
6
-
7
- const fsReadFile = promisify(fs.readFile)
8
- const fsUnlink = promisify(fs.unlink)
9
- const fsWriteFile = promisify(fs.writeFile)
10
-
11
- /**
12
- * Read contents of a file to buffer
13
- * @param {String} filename
14
- * @param {Boolean} [ignoreENOENT=true] — Whether to throw if the file doesn't exist
15
- * @returns {Promise<Buffer>}
16
- */
17
- const readFile = async (filename, ignoreENOENT = true) => {
18
- debug('Reading file `%s`', filename)
19
- try {
20
- return await fsReadFile(filename)
21
- } catch (error) {
22
- if (ignoreENOENT && error.code === 'ENOENT') {
23
- debug("File `%s` doesn't exist, ignoring...", filename)
24
- return null // no-op file doesn't exist
25
- } else {
26
- throw error
27
- }
28
- }
29
- }
30
-
31
- /**
32
- * Remove a file
33
- * @param {String} filename
34
- * @param {Boolean} [ignoreENOENT=true] — Whether to throw if the file doesn't exist
35
- */
36
- const unlink = async (filename, ignoreENOENT = true) => {
37
- debug('Removing file `%s`', filename)
38
- try {
39
- await fsUnlink(filename)
40
- } catch (error) {
41
- if (ignoreENOENT && error.code === 'ENOENT') {
42
- debug("File `%s` doesn't exist, ignoring...", filename)
43
- } else {
44
- throw error
45
- }
46
- }
47
- }
48
-
49
- /**
50
- * Write buffer to file
51
- * @param {String} filename
52
- * @param {Buffer} buffer
53
- */
54
- const writeFile = async (filename, buffer) => {
55
- debug('Writing file `%s`', filename)
56
- await fsWriteFile(filename, buffer)
57
- }
58
-
59
- module.exports = {
60
- readFile,
61
- unlink,
62
- writeFile,
63
- }