lint-staged 9.5.0-beta.2 → 9.5.0
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 +51 -22
- package/bin/lint-staged +8 -2
- package/package.json +8 -7
- package/src/execGit.js +13 -0
- package/{lib → src}/generateTasks.js +1 -0
- package/{lib → src}/getStagedFiles.js +0 -0
- package/src/gitWorkflow.js +164 -0
- package/{lib → src}/index.js +11 -2
- package/{lib → src}/makeCmdTasks.js +3 -6
- package/{lib → src}/printErrors.js +0 -0
- package/{lib → src}/resolveGitDir.js +0 -0
- package/{lib → src}/resolveTaskFn.js +42 -13
- package/src/runAll.js +147 -0
- package/{lib → src}/validateConfig.js +0 -0
- package/lib/execGit.js +0 -17
- package/lib/file.js +0 -51
- package/lib/gitWorkflow.js +0 -184
- package/lib/runAll.js +0 -168
package/README.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Run linters against staged git files and don't let :poop: slip into your code base!
|
|
4
4
|
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚧 Help test `lint-staged@beta`!
|
|
8
|
+
|
|
9
|
+
Version 10 of `lint-staged` is coming with changes that help it run faster on large git repositories and prevent loss of data during errors. Please help test the `beta` version and report any inconsistencies in our [GitHub Issues](https://github.com/okonet/lint-staged/issues):
|
|
10
|
+
|
|
11
|
+
**Using npm**
|
|
12
|
+
|
|
13
|
+
npm install --save-dev lint-staged@beta
|
|
14
|
+
|
|
15
|
+
**Using yarn**
|
|
16
|
+
|
|
17
|
+
yarn add -D lint-staged@beta
|
|
18
|
+
|
|
19
|
+
### Notable changes
|
|
20
|
+
|
|
21
|
+
- A git stash is created before running any tasks, so in case of errors any lost changes can be restored easily (and automatically unless lint-staged itself crashes)
|
|
22
|
+
- Instead of write-tree/read-tree, `lint-staged@beta` uses git stashes to hide unstaged changes while running tasks against staged files
|
|
23
|
+
- This results in a performance increase of up to 45x on very large repositories
|
|
24
|
+
- The behaviour of committing modifications during tasks (eg. `prettier --write && git add`) is different. The current version creates a diff of these modifications, and applies it against the original state, silently ignoring any errors. The `beta` version leaves modifications of staged files as-is, and then restores all hidden unstaged changes as patch. If applying the patch fails due to a merge conflict (because tasks have modified the same lines), a 3-way merge will be retried. If this also fails, the entire commit will fail and the original state will be restored.
|
|
25
|
+
- **TL;DR** the `beta` version will never skip committing any changes by tasks (due to a merge conflict), but might fail in very complex situations where unstaged changes cannot be restored cleanly. If this happens to you, we are very interested in a repeatable test scenario.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
5
29
|
[](https://asciinema.org/a/199934)
|
|
6
30
|
|
|
7
31
|
## Why
|
|
@@ -46,13 +70,14 @@ $ npx lint-staged --help
|
|
|
46
70
|
Usage: lint-staged [options]
|
|
47
71
|
|
|
48
72
|
Options:
|
|
49
|
-
-V, --version
|
|
50
|
-
-c, --config [path]
|
|
51
|
-
-r, --relative
|
|
52
|
-
-x, --shell
|
|
53
|
-
-q, --quiet
|
|
54
|
-
-d, --debug
|
|
55
|
-
-
|
|
73
|
+
-V, --version output the version number
|
|
74
|
+
-c, --config [path] Path to configuration file
|
|
75
|
+
-r, --relative Pass relative filepaths to tasks
|
|
76
|
+
-x, --shell Skip parsing of tasks for better shell support
|
|
77
|
+
-q, --quiet Disable lint-staged’s own console output
|
|
78
|
+
-d, --debug Enable debug mode
|
|
79
|
+
-p, --concurrent [parallel tasks] The number of tasks to run concurrently, or false to run tasks sequentially
|
|
80
|
+
-h, --help output usage information
|
|
56
81
|
```
|
|
57
82
|
|
|
58
83
|
- **`--config [path]`**: This can be used to manually specify the `lint-staged` config file location. However, if the specified file cannot be found, it will error out instead of performing the usual search. You may pass a npm package name for configuration also.
|
|
@@ -62,6 +87,10 @@ Options:
|
|
|
62
87
|
- **`--debug`**: Enabling the debug mode does the following:
|
|
63
88
|
- `lint-staged` uses the [debug](https://github.com/visionmedia/debug) module internally to log information about staged files, commands being executed, location of binaries, etc. Debug logs, which are automatically enabled by passing the flag, can also be enabled by setting the environment variable `$DEBUG` to `lint-staged*`.
|
|
64
89
|
- Use the [`verbose` renderer](https://github.com/SamVerschueren/listr-verbose-renderer) for `listr`.
|
|
90
|
+
- **`--concurrent [number | (true/false)]`**: Controls the concurrency of tasks being run by lint-staged. **NOTE**: This does NOT affect the concurrency of subtasks (they will always be run sequentially). Possible values are:
|
|
91
|
+
- `false`: Run all tasks serially
|
|
92
|
+
- `true` (default) : _Infinite_ concurrency. Runs as many tasks in parallel as possible.
|
|
93
|
+
- `{number}`: Run the specified number of tasks in parallel, where `1` is equivalent to `false`.
|
|
65
94
|
|
|
66
95
|
## Configuration
|
|
67
96
|
|
|
@@ -108,7 +137,7 @@ Linter commands work on a subset of all staged files, defined by a _glob pattern
|
|
|
108
137
|
- **`"*.js"`** will match all JS files, like `/test.js` and `/foo/bar/test.js`
|
|
109
138
|
- **`"!(*test).js"`**. will match all JS files, except those ending in `test.js`, so `foo.js` but not `foo.test.js`
|
|
110
139
|
- If the glob pattern does contain a slash (`/`), it will match for paths as well:
|
|
111
|
-
- **`"
|
|
140
|
+
- **`"./*.js"`** will match all JS files in the git repo root, so `/test.js` but not `/foo/bar/test.js`
|
|
112
141
|
- **`"foo/**/\*.js"`** will match all JS files inside the`/foo`directory, so`/foo/bar/test.js`but not`/test.js`
|
|
113
142
|
|
|
114
143
|
When matching, `lint-staged` will do the following
|
|
@@ -229,15 +258,15 @@ module.exports = {
|
|
|
229
258
|
|
|
230
259
|
## Reformatting the code
|
|
231
260
|
|
|
232
|
-
Tools like [Prettier](https://prettier.io), ESLint/TSLint, or stylelint can reformat your code according to an appropriate config by running `prettier --write`/`eslint --fix`/`tslint --fix`/`stylelint --fix`.
|
|
261
|
+
Tools like [Prettier](https://prettier.io), ESLint/TSLint, or stylelint can reformat your code according to an appropriate config by running `prettier --write`/`eslint --fix`/`tslint --fix`/`stylelint --fix`. After the code is reformatted, we want it to be added to the same commit. This can be done using following config:
|
|
233
262
|
|
|
234
263
|
```json
|
|
235
264
|
{
|
|
236
|
-
"*.js": "prettier --write"
|
|
265
|
+
"*.js": ["prettier --write", "git add"]
|
|
237
266
|
}
|
|
238
267
|
```
|
|
239
268
|
|
|
240
|
-
|
|
269
|
+
Starting from v8, lint-staged will stash your remaining changes (not added to the index) and restore them from stash afterwards if there are partially staged files detected. This allows you to create partial commits with hunks using `git add --patch`. See the [blog post](https://medium.com/@okonetchnikov/announcing-lint-staged-with-support-for-partially-staged-files-abc24a40d3ff)
|
|
241
270
|
|
|
242
271
|
## Examples
|
|
243
272
|
|
|
@@ -273,7 +302,7 @@ _Note we don’t pass a path as an argument for the runners. This is important s
|
|
|
273
302
|
|
|
274
303
|
```json
|
|
275
304
|
{
|
|
276
|
-
"*.js": "eslint --fix"
|
|
305
|
+
"*.js": ["eslint --fix", "git add"]
|
|
277
306
|
}
|
|
278
307
|
```
|
|
279
308
|
|
|
@@ -285,7 +314,7 @@ If you wish to reuse a npm script defined in your package.json:
|
|
|
285
314
|
|
|
286
315
|
```json
|
|
287
316
|
{
|
|
288
|
-
"*.js": "npm run my-custom-script --"
|
|
317
|
+
"*.js": ["npm run my-custom-script --", "git add"]
|
|
289
318
|
}
|
|
290
319
|
```
|
|
291
320
|
|
|
@@ -293,7 +322,7 @@ The following is equivalent:
|
|
|
293
322
|
|
|
294
323
|
```json
|
|
295
324
|
{
|
|
296
|
-
"*.js": "linter --arg1 --arg2"
|
|
325
|
+
"*.js": ["linter --arg1 --arg2", "git add"]
|
|
297
326
|
}
|
|
298
327
|
```
|
|
299
328
|
|
|
@@ -313,19 +342,19 @@ For example, here is `jest` running on all `.js` files with the `NODE_ENV` varia
|
|
|
313
342
|
|
|
314
343
|
```json
|
|
315
344
|
{
|
|
316
|
-
"*.{js,jsx}": "prettier --write"
|
|
345
|
+
"*.{js,jsx}": ["prettier --write", "git add"]
|
|
317
346
|
}
|
|
318
347
|
```
|
|
319
348
|
|
|
320
349
|
```json
|
|
321
350
|
{
|
|
322
|
-
"*.{ts,tsx}": "prettier --write"
|
|
351
|
+
"*.{ts,tsx}": ["prettier --write", "git add"]
|
|
323
352
|
}
|
|
324
353
|
```
|
|
325
354
|
|
|
326
355
|
```json
|
|
327
356
|
{
|
|
328
|
-
"*.{md,html}": "prettier --write"
|
|
357
|
+
"*.{md,html}": ["prettier --write", "git add"]
|
|
329
358
|
}
|
|
330
359
|
```
|
|
331
360
|
|
|
@@ -338,19 +367,19 @@ For example, here is `jest` running on all `.js` files with the `NODE_ENV` varia
|
|
|
338
367
|
}
|
|
339
368
|
```
|
|
340
369
|
|
|
341
|
-
### Run PostCSS sorting and Stylelint to check
|
|
370
|
+
### Run PostCSS sorting, add files to commit and run Stylelint to check
|
|
342
371
|
|
|
343
372
|
```json
|
|
344
373
|
{
|
|
345
|
-
"*.scss": "postcss --config path/to/your/config --replace", "stylelint"
|
|
374
|
+
"*.scss": ["postcss --config path/to/your/config --replace", "stylelint", "git add"]
|
|
346
375
|
}
|
|
347
376
|
```
|
|
348
377
|
|
|
349
|
-
### Minify the images
|
|
378
|
+
### Minify the images and add files to commit
|
|
350
379
|
|
|
351
380
|
```json
|
|
352
381
|
{
|
|
353
|
-
"*.{png,jpeg,jpg,gif,svg}": "imagemin-lint-staged"
|
|
382
|
+
"*.{png,jpeg,jpg,gif,svg}": ["imagemin-lint-staged", "git add"]
|
|
354
383
|
}
|
|
355
384
|
```
|
|
356
385
|
|
|
@@ -367,7 +396,7 @@ See more on [this blog post](https://medium.com/@tomchentw/imagemin-lint-staged-
|
|
|
367
396
|
|
|
368
397
|
```json
|
|
369
398
|
{
|
|
370
|
-
"*.{js,jsx}": "flow focus-check"
|
|
399
|
+
"*.{js,jsx}": ["flow focus-check", "git add"]
|
|
371
400
|
}
|
|
372
401
|
```
|
|
373
402
|
|
package/bin/lint-staged
CHANGED
|
@@ -23,7 +23,7 @@ require('please-upgrade-node')(
|
|
|
23
23
|
|
|
24
24
|
const cmdline = require('commander')
|
|
25
25
|
const debugLib = require('debug')
|
|
26
|
-
const lintStaged = require('../
|
|
26
|
+
const lintStaged = require('../src')
|
|
27
27
|
|
|
28
28
|
const debug = debugLib('lint-staged:bin')
|
|
29
29
|
|
|
@@ -34,6 +34,11 @@ cmdline
|
|
|
34
34
|
.option('-x, --shell', 'Skip parsing of tasks for better shell support')
|
|
35
35
|
.option('-q, --quiet', 'Disable lint-staged’s own console output')
|
|
36
36
|
.option('-d, --debug', 'Enable debug mode')
|
|
37
|
+
.option(
|
|
38
|
+
'-p, --concurrent <parallel tasks>',
|
|
39
|
+
'The number of tasks to run concurrently, or false to run tasks serially',
|
|
40
|
+
true
|
|
41
|
+
)
|
|
37
42
|
.parse(process.argv)
|
|
38
43
|
|
|
39
44
|
if (cmdline.debug) {
|
|
@@ -47,7 +52,8 @@ lintStaged({
|
|
|
47
52
|
relative: !!cmdline.relative,
|
|
48
53
|
shell: !!cmdline.shell,
|
|
49
54
|
quiet: !!cmdline.quiet,
|
|
50
|
-
debug: !!cmdline.debug
|
|
55
|
+
debug: !!cmdline.debug,
|
|
56
|
+
concurrent: cmdline.concurrent
|
|
51
57
|
})
|
|
52
58
|
.then(passed => {
|
|
53
59
|
process.exitCode = passed ? 0 : 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lint-staged",
|
|
3
|
-
"version": "9.5.0
|
|
3
|
+
"version": "9.5.0",
|
|
4
4
|
"description": "Lint files staged by git",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "https://github.com/okonet/lint-staged",
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
"Suhas Karanth <sudo.suhas@gmail.com>",
|
|
11
11
|
"Iiro Jäppinen <iiro@jappinen.fi> (https://iiro.fi)"
|
|
12
12
|
],
|
|
13
|
+
"main": "./src/index.js",
|
|
13
14
|
"bin": "./bin/lint-staged",
|
|
14
|
-
"main": "./lib/index.js",
|
|
15
15
|
"files": [
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"src",
|
|
17
|
+
"bin"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"cz": "git-cz",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"micromatch": "^4.0.2",
|
|
44
44
|
"normalize-path": "^3.0.0",
|
|
45
45
|
"please-upgrade-node": "^3.1.1",
|
|
46
|
+
"string-argv": "^0.3.0",
|
|
46
47
|
"stringify-object": "^3.3.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
@@ -68,8 +69,8 @@
|
|
|
68
69
|
"jest": "^24.8.0",
|
|
69
70
|
"jest-snapshot-serializer-ansi": "^1.0.0",
|
|
70
71
|
"jsonlint": "^1.6.3",
|
|
71
|
-
"
|
|
72
|
-
"
|
|
72
|
+
"prettier": "1.18.2",
|
|
73
|
+
"tmp": "0.1.0"
|
|
73
74
|
},
|
|
74
75
|
"config": {
|
|
75
76
|
"commitizen": {
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
"jest": {
|
|
80
81
|
"collectCoverage": true,
|
|
81
82
|
"collectCoverageFrom": [
|
|
82
|
-
"
|
|
83
|
+
"src/**/*.js"
|
|
83
84
|
],
|
|
84
85
|
"setupFiles": [
|
|
85
86
|
"./testSetup.js"
|
package/src/execGit.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const debug = require('debug')('lint-staged:git')
|
|
4
|
+
const execa = require('execa')
|
|
5
|
+
|
|
6
|
+
module.exports = async function execGit(cmd, options = {}) {
|
|
7
|
+
debug('Running git command', cmd)
|
|
8
|
+
const { stdout } = await execa('git', [].concat(cmd), {
|
|
9
|
+
...options,
|
|
10
|
+
cwd: options.cwd || process.cwd()
|
|
11
|
+
})
|
|
12
|
+
return stdout
|
|
13
|
+
}
|
|
@@ -15,6 +15,7 @@ const debug = require('debug')('lint-staged:gen-tasks')
|
|
|
15
15
|
* @param {boolean} [options.gitDir] - Git root directory
|
|
16
16
|
* @param {boolean} [options.files] - Staged filepaths
|
|
17
17
|
* @param {boolean} [options.relative] - Whether filepaths to should be relative to gitDir
|
|
18
|
+
* @returns {Promise}
|
|
18
19
|
*/
|
|
19
20
|
module.exports = function generateTasks({
|
|
20
21
|
config,
|
|
File without changes
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const del = require('del')
|
|
4
|
+
const debug = require('debug')('lint-staged:git')
|
|
5
|
+
|
|
6
|
+
const execGit = require('./execGit')
|
|
7
|
+
|
|
8
|
+
let workingCopyTree = null
|
|
9
|
+
let indexTree = null
|
|
10
|
+
let formattedIndexTree = null
|
|
11
|
+
|
|
12
|
+
async function writeTree(options) {
|
|
13
|
+
return execGit(['write-tree'], options)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function getDiffForTrees(tree1, tree2, options) {
|
|
17
|
+
debug(`Generating diff between trees ${tree1} and ${tree2}...`)
|
|
18
|
+
return execGit(
|
|
19
|
+
[
|
|
20
|
+
'diff-tree',
|
|
21
|
+
'--ignore-submodules',
|
|
22
|
+
'--binary',
|
|
23
|
+
'--no-color',
|
|
24
|
+
'--no-ext-diff',
|
|
25
|
+
'--unified=0',
|
|
26
|
+
tree1,
|
|
27
|
+
tree2
|
|
28
|
+
],
|
|
29
|
+
options
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function hasPartiallyStagedFiles(options) {
|
|
34
|
+
const stdout = await execGit(['status', '--porcelain'], options)
|
|
35
|
+
if (!stdout) return false
|
|
36
|
+
|
|
37
|
+
const changedFiles = stdout.split('\n')
|
|
38
|
+
const partiallyStaged = changedFiles.filter(line => {
|
|
39
|
+
/**
|
|
40
|
+
* See https://git-scm.com/docs/git-status#_short_format
|
|
41
|
+
* The first letter of the line represents current index status,
|
|
42
|
+
* and second the working tree
|
|
43
|
+
*/
|
|
44
|
+
const [index, workingTree] = line
|
|
45
|
+
return index !== ' ' && workingTree !== ' ' && index !== '?' && workingTree !== '?'
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return partiallyStaged.length > 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// eslint-disable-next-line
|
|
52
|
+
async function gitStashSave(options) {
|
|
53
|
+
debug('Stashing files...')
|
|
54
|
+
// Save ref to the current index
|
|
55
|
+
indexTree = await writeTree(options)
|
|
56
|
+
// Add working copy changes to index
|
|
57
|
+
await execGit(['add', '.'], options)
|
|
58
|
+
// Save ref to the working copy index
|
|
59
|
+
workingCopyTree = await writeTree(options)
|
|
60
|
+
// Restore the current index
|
|
61
|
+
await execGit(['read-tree', indexTree], options)
|
|
62
|
+
// Remove all modifications
|
|
63
|
+
await execGit(['checkout-index', '-af'], options)
|
|
64
|
+
// await execGit(['clean', '-dfx'], options)
|
|
65
|
+
debug('Done stashing files!')
|
|
66
|
+
return [workingCopyTree, indexTree]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function updateStash(options) {
|
|
70
|
+
formattedIndexTree = await writeTree(options)
|
|
71
|
+
return formattedIndexTree
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function applyPatchFor(tree1, tree2, options) {
|
|
75
|
+
const diff = await getDiffForTrees(tree1, tree2, options)
|
|
76
|
+
/**
|
|
77
|
+
* This is crucial for patch to work
|
|
78
|
+
* For some reason, git-apply requires that the patch ends with the newline symbol
|
|
79
|
+
* See http://git.661346.n2.nabble.com/Bug-in-Git-Gui-Creates-corrupt-patch-td2384251.html
|
|
80
|
+
* and https://stackoverflow.com/questions/13223868/how-to-stage-line-by-line-in-git-gui-although-no-newline-at-end-of-file-warnin
|
|
81
|
+
*/
|
|
82
|
+
// TODO: Figure out how to test this. For some reason tests were working but in the real env it was failing
|
|
83
|
+
if (diff) {
|
|
84
|
+
try {
|
|
85
|
+
/**
|
|
86
|
+
* Apply patch to index. We will apply it with --reject so it it will try apply hunk by hunk
|
|
87
|
+
* We're not interested in failied hunks since this mean that formatting conflicts with user changes
|
|
88
|
+
* and we prioritize user changes over formatter's
|
|
89
|
+
*/
|
|
90
|
+
await execGit(
|
|
91
|
+
['apply', '-v', '--whitespace=nowarn', '--reject', '--recount', '--unidiff-zero'],
|
|
92
|
+
{
|
|
93
|
+
...options,
|
|
94
|
+
input: `${diff}\n` // TODO: This should also work on Windows but test would be good
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
} catch (err) {
|
|
98
|
+
debug('Could not apply patch to the stashed files cleanly')
|
|
99
|
+
debug(err)
|
|
100
|
+
debug('Patch content:')
|
|
101
|
+
debug(diff)
|
|
102
|
+
throw new Error('Could not apply patch to the stashed files cleanly.', err)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function gitStashPop(options) {
|
|
108
|
+
if (workingCopyTree === null) {
|
|
109
|
+
throw new Error('Trying to restore from stash but could not find working copy stash.')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
debug('Restoring working copy')
|
|
113
|
+
// Restore the stashed files in the index
|
|
114
|
+
await execGit(['read-tree', workingCopyTree], options)
|
|
115
|
+
// and sync it to the working copy (i.e. update files on fs)
|
|
116
|
+
await execGit(['checkout-index', '-af'], options)
|
|
117
|
+
|
|
118
|
+
// Then, restore the index after working copy is restored
|
|
119
|
+
if (indexTree !== null && formattedIndexTree === null) {
|
|
120
|
+
// Restore changes that were in index if there are no formatting changes
|
|
121
|
+
debug('Restoring index')
|
|
122
|
+
await execGit(['read-tree', indexTree], options)
|
|
123
|
+
} else {
|
|
124
|
+
/**
|
|
125
|
+
* There are formatting changes we want to restore in the index
|
|
126
|
+
* and in the working copy. So we start by restoring the index
|
|
127
|
+
* and after that we'll try to carry as many as possible changes
|
|
128
|
+
* to the working copy by applying the patch with --reject option.
|
|
129
|
+
*/
|
|
130
|
+
debug('Restoring index with formatting changes')
|
|
131
|
+
await execGit(['read-tree', formattedIndexTree], options)
|
|
132
|
+
try {
|
|
133
|
+
await applyPatchFor(indexTree, formattedIndexTree, options)
|
|
134
|
+
} catch (err) {
|
|
135
|
+
debug(
|
|
136
|
+
'Found conflicts between formatters and local changes. Formatters changes will be ignored for conflicted hunks.'
|
|
137
|
+
)
|
|
138
|
+
/**
|
|
139
|
+
* Clean up working directory from *.rej files that contain conflicted hanks.
|
|
140
|
+
* These hunks are coming from formatters so we'll just delete them since they are irrelevant.
|
|
141
|
+
*/
|
|
142
|
+
try {
|
|
143
|
+
const rejFiles = await del(['*.rej'], options)
|
|
144
|
+
debug('Deleted files and folders:\n', rejFiles.join('\n'))
|
|
145
|
+
} catch (delErr) {
|
|
146
|
+
debug('Error deleting *.rej files', delErr)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Clean up references
|
|
151
|
+
workingCopyTree = null
|
|
152
|
+
indexTree = null
|
|
153
|
+
formattedIndexTree = null
|
|
154
|
+
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
execGit,
|
|
160
|
+
gitStashSave,
|
|
161
|
+
gitStashPop,
|
|
162
|
+
hasPartiallyStagedFiles,
|
|
163
|
+
updateStash
|
|
164
|
+
}
|
package/{lib → src}/index.js
RENAMED
|
@@ -48,12 +48,21 @@ function loadConfig(configPath) {
|
|
|
48
48
|
* @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
|
|
49
49
|
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
|
|
50
50
|
* @param {boolean} [options.debug] - Enable debug mode
|
|
51
|
+
* @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
|
|
51
52
|
* @param {Logger} [logger]
|
|
52
53
|
*
|
|
53
54
|
* @returns {Promise<boolean>} Promise of whether the linting passed or failed
|
|
54
55
|
*/
|
|
55
56
|
module.exports = function lintStaged(
|
|
56
|
-
{
|
|
57
|
+
{
|
|
58
|
+
configPath,
|
|
59
|
+
config,
|
|
60
|
+
relative = false,
|
|
61
|
+
shell = false,
|
|
62
|
+
quiet = false,
|
|
63
|
+
debug = false,
|
|
64
|
+
concurrent = true
|
|
65
|
+
} = {},
|
|
57
66
|
logger = console
|
|
58
67
|
) {
|
|
59
68
|
debugLog('Loading config using `cosmiconfig`')
|
|
@@ -76,7 +85,7 @@ module.exports = function lintStaged(
|
|
|
76
85
|
debugLog('lint-staged config:\n%O', config)
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
return runAll({ config, relative, shell, quiet, debug }, logger)
|
|
88
|
+
return runAll({ config, relative, shell, quiet, debug, concurrent }, logger)
|
|
80
89
|
.then(() => {
|
|
81
90
|
debugLog('tasks were executed successfully!')
|
|
82
91
|
return Promise.resolve(true)
|
|
@@ -13,7 +13,7 @@ const debug = require('debug')('lint-staged:make-cmd-tasks')
|
|
|
13
13
|
* @param {string} options.gitDir
|
|
14
14
|
* @param {Boolean} shell
|
|
15
15
|
*/
|
|
16
|
-
module.exports = function makeCmdTasks({ commands, files, gitDir, shell }) {
|
|
16
|
+
module.exports = async function makeCmdTasks({ commands, files, gitDir, shell }) {
|
|
17
17
|
debug('Creating listr tasks for commands %o', commands)
|
|
18
18
|
const commandsArray = Array.isArray(commands) ? commands : [commands]
|
|
19
19
|
|
|
@@ -41,11 +41,8 @@ module.exports = function makeCmdTasks({ commands, files, gitDir, shell }) {
|
|
|
41
41
|
title = mockCommands[i].replace(/\[file\].*\[file\]/, '[file]')
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
command,
|
|
47
|
-
task: resolveTaskFn({ command, files, gitDir, isFn, shell })
|
|
48
|
-
})
|
|
44
|
+
const task = { title, task: resolveTaskFn({ gitDir, isFn, command, files, shell }) }
|
|
45
|
+
tasks.push(task)
|
|
49
46
|
})
|
|
50
47
|
|
|
51
48
|
return tasks
|
|
File without changes
|
|
File without changes
|
|
@@ -4,9 +4,27 @@ const chalk = require('chalk')
|
|
|
4
4
|
const dedent = require('dedent')
|
|
5
5
|
const execa = require('execa')
|
|
6
6
|
const symbols = require('log-symbols')
|
|
7
|
+
const stringArgv = require('string-argv')
|
|
7
8
|
|
|
8
9
|
const debug = require('debug')('lint-staged:task')
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Execute the given linter cmd using execa and
|
|
13
|
+
* return the promise.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} cmd
|
|
16
|
+
* @param {Array<string>} args
|
|
17
|
+
* @param {Object} execaOptions
|
|
18
|
+
* @return {Promise} child_process
|
|
19
|
+
*/
|
|
20
|
+
const execLinter = (cmd, args, execaOptions) => {
|
|
21
|
+
debug('cmd:', cmd)
|
|
22
|
+
if (args) debug('args:', args)
|
|
23
|
+
debug('execaOptions:', execaOptions)
|
|
24
|
+
|
|
25
|
+
return args ? execa(cmd, args, execaOptions) : execa(cmd, execaOptions)
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
const successMsg = linter => `${symbols.success} ${linter} passed!`
|
|
11
29
|
|
|
12
30
|
/**
|
|
@@ -55,22 +73,21 @@ function makeErr(linter, result, context = {}) {
|
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
/**
|
|
58
|
-
* Returns the task function for the linter.
|
|
76
|
+
* Returns the task function for the linter. It handles chunking for file paths
|
|
77
|
+
* if the OS is Windows.
|
|
59
78
|
*
|
|
60
79
|
* @param {Object} options
|
|
61
80
|
* @param {string} options.command — Linter task
|
|
62
81
|
* @param {String} options.gitDir - Current git repo path
|
|
63
82
|
* @param {Boolean} options.isFn - Whether the linter task is a function
|
|
64
|
-
* @param {Array<string>} options.
|
|
83
|
+
* @param {Array<string>} options.pathsToLint — Filepaths to run the linter task against
|
|
65
84
|
* @param {Boolean} [options.relative] — Whether the filepaths should be relative
|
|
66
85
|
* @param {Boolean} [options.shell] — Whether to skip parsing linter task for better shell support
|
|
67
86
|
* @returns {function(): Promise<Array<string>>}
|
|
68
87
|
*/
|
|
69
88
|
module.exports = function resolveTaskFn({ command, files, gitDir, isFn, relative, shell = false }) {
|
|
70
|
-
const cmd = isFn ? command : `${command} ${files.join(' ')}`
|
|
71
|
-
debug('cmd:', cmd)
|
|
72
|
-
|
|
73
89
|
const execaOptions = { preferLocal: true, reject: false, shell }
|
|
90
|
+
|
|
74
91
|
if (relative) {
|
|
75
92
|
execaOptions.cwd = process.cwd()
|
|
76
93
|
} else if (/^git(\.exe)?/i.test(command) && gitDir !== process.cwd()) {
|
|
@@ -78,15 +95,27 @@ module.exports = function resolveTaskFn({ command, files, gitDir, isFn, relative
|
|
|
78
95
|
// e.g `npm` should run tasks in the actual CWD
|
|
79
96
|
execaOptions.cwd = gitDir
|
|
80
97
|
}
|
|
81
|
-
debug('execaOptions:', execaOptions)
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
|
|
99
|
+
let cmd
|
|
100
|
+
let args
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
102
|
+
if (shell) {
|
|
103
|
+
execaOptions.shell = true
|
|
104
|
+
// If `shell`, passed command shouldn't be parsed
|
|
105
|
+
// If `linter` is a function, command already includes `files`.
|
|
106
|
+
cmd = isFn ? command : `${command} ${files.join(' ')}`
|
|
107
|
+
} else {
|
|
108
|
+
const [parsedCmd, ...parsedArgs] = stringArgv.parseArgsStringToArgv(command)
|
|
109
|
+
cmd = parsedCmd
|
|
110
|
+
args = isFn ? parsedArgs : parsedArgs.concat(files)
|
|
91
111
|
}
|
|
112
|
+
|
|
113
|
+
return ctx =>
|
|
114
|
+
execLinter(cmd, args, execaOptions).then(result => {
|
|
115
|
+
if (result.failed || result.killed || result.signal != null) {
|
|
116
|
+
throw makeErr(command, result, ctx)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return successMsg(command)
|
|
120
|
+
})
|
|
92
121
|
}
|
package/src/runAll.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/** @typedef {import('./index').Logger} Logger */
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk')
|
|
6
|
+
const dedent = require('dedent')
|
|
7
|
+
const Listr = require('listr')
|
|
8
|
+
const symbols = require('log-symbols')
|
|
9
|
+
|
|
10
|
+
const generateTasks = require('./generateTasks')
|
|
11
|
+
const getStagedFiles = require('./getStagedFiles')
|
|
12
|
+
const git = require('./gitWorkflow')
|
|
13
|
+
const makeCmdTasks = require('./makeCmdTasks')
|
|
14
|
+
const resolveGitDir = require('./resolveGitDir')
|
|
15
|
+
|
|
16
|
+
const debugLog = require('debug')('lint-staged:run')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* https://serverfault.com/questions/69430/what-is-the-maximum-length-of-a-command-line-in-mac-os-x
|
|
20
|
+
* https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation
|
|
21
|
+
* https://unix.stackexchange.com/a/120652
|
|
22
|
+
*/
|
|
23
|
+
const MAX_ARG_LENGTH =
|
|
24
|
+
(process.platform === 'darwin' && 262144) || (process.platform === 'win32' && 8191) || 131072
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Executes all tasks and either resolves or rejects the promise
|
|
28
|
+
*
|
|
29
|
+
* @param {object} options
|
|
30
|
+
* @param {Object} [options.config] - Task configuration
|
|
31
|
+
* @param {Object} [options.cwd] - Current working directory
|
|
32
|
+
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
|
|
33
|
+
* @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
|
|
34
|
+
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
|
|
35
|
+
* @param {boolean} [options.debug] - Enable debug mode
|
|
36
|
+
* @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
|
|
37
|
+
* @param {Logger} logger
|
|
38
|
+
* @returns {Promise}
|
|
39
|
+
*/
|
|
40
|
+
module.exports = async function runAll(
|
|
41
|
+
{
|
|
42
|
+
config,
|
|
43
|
+
cwd = process.cwd(),
|
|
44
|
+
debug = false,
|
|
45
|
+
quiet = false,
|
|
46
|
+
relative = false,
|
|
47
|
+
shell = false,
|
|
48
|
+
concurrent = true
|
|
49
|
+
},
|
|
50
|
+
logger = console
|
|
51
|
+
) {
|
|
52
|
+
debugLog('Running all linter scripts')
|
|
53
|
+
const gitDir = await resolveGitDir({ cwd })
|
|
54
|
+
|
|
55
|
+
if (!gitDir) {
|
|
56
|
+
throw new Error('Current directory is not a git directory!')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
debugLog('Resolved git directory to be `%s`', gitDir)
|
|
60
|
+
|
|
61
|
+
const files = await getStagedFiles({ cwd: gitDir })
|
|
62
|
+
|
|
63
|
+
if (!files) {
|
|
64
|
+
throw new Error('Unable to get staged files!')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
debugLog('Loaded list of staged files in git:\n%O', files)
|
|
68
|
+
|
|
69
|
+
const argLength = files.join(' ').length
|
|
70
|
+
if (argLength > MAX_ARG_LENGTH) {
|
|
71
|
+
logger.warn(
|
|
72
|
+
dedent`${symbols.warning} ${chalk.yellow(
|
|
73
|
+
`lint-staged generated an argument string of ${argLength} characters, and commands might not run correctly on your platform.
|
|
74
|
+
It is recommended to use functions as linters and split your command based on the number of staged files. For more info, please visit:
|
|
75
|
+
https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-commands`
|
|
76
|
+
)}`
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const tasks = generateTasks({ config, cwd, gitDir, files, relative }).map(task => ({
|
|
81
|
+
title: `Running tasks for ${task.pattern}`,
|
|
82
|
+
task: async () =>
|
|
83
|
+
new Listr(
|
|
84
|
+
await makeCmdTasks({ commands: task.commands, files: task.fileList, gitDir, shell }),
|
|
85
|
+
{
|
|
86
|
+
// In sub-tasks we don't want to run concurrently
|
|
87
|
+
// and we want to abort on errors
|
|
88
|
+
dateFormat: false,
|
|
89
|
+
concurrent: false,
|
|
90
|
+
exitOnError: true
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
skip: () => {
|
|
94
|
+
if (task.fileList.length === 0) {
|
|
95
|
+
return `No staged files match ${task.pattern}`
|
|
96
|
+
}
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
}))
|
|
100
|
+
|
|
101
|
+
const listrOptions = {
|
|
102
|
+
dateFormat: false,
|
|
103
|
+
renderer: (quiet && 'silent') || (debug && 'verbose') || 'update'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If all of the configured "linters" should be skipped
|
|
107
|
+
// avoid executing any lint-staged logic
|
|
108
|
+
if (tasks.every(task => task.skip())) {
|
|
109
|
+
logger.log('No staged files match any of provided globs.')
|
|
110
|
+
return 'No tasks to run.'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return new Listr(
|
|
114
|
+
[
|
|
115
|
+
{
|
|
116
|
+
title: 'Stashing changes...',
|
|
117
|
+
skip: async () => {
|
|
118
|
+
const hasPSF = await git.hasPartiallyStagedFiles({ cwd: gitDir })
|
|
119
|
+
if (!hasPSF) {
|
|
120
|
+
return 'No partially staged files found...'
|
|
121
|
+
}
|
|
122
|
+
return false
|
|
123
|
+
},
|
|
124
|
+
task: ctx => {
|
|
125
|
+
ctx.hasStash = true
|
|
126
|
+
return git.gitStashSave({ cwd: gitDir })
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
title: 'Running tasks...',
|
|
131
|
+
task: () => new Listr(tasks, { ...listrOptions, concurrent, exitOnError: false })
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
title: 'Updating stash...',
|
|
135
|
+
enabled: ctx => ctx.hasStash,
|
|
136
|
+
skip: ctx => ctx.hasErrors && 'Skipping stash update since some tasks exited with errors',
|
|
137
|
+
task: () => git.updateStash({ cwd: gitDir })
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
title: 'Restoring local changes...',
|
|
141
|
+
enabled: ctx => ctx.hasStash,
|
|
142
|
+
task: () => git.gitStashPop({ cwd: gitDir })
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
listrOptions
|
|
146
|
+
).run()
|
|
147
|
+
}
|
|
File without changes
|
package/lib/execGit.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const debug = require('debug')('lint-staged:git')
|
|
4
|
-
const execa = require('execa')
|
|
5
|
-
|
|
6
|
-
module.exports = async function execGit(cmd, options = {}) {
|
|
7
|
-
debug('Running git command', cmd)
|
|
8
|
-
try {
|
|
9
|
-
const { stdout } = await execa('git', [].concat(cmd), {
|
|
10
|
-
...options,
|
|
11
|
-
cwd: options.cwd || process.cwd()
|
|
12
|
-
})
|
|
13
|
-
return stdout
|
|
14
|
-
} catch ({ all }) {
|
|
15
|
-
throw new Error(all)
|
|
16
|
-
}
|
|
17
|
-
}
|
package/lib/file.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const debug = require('debug')('lint-staged:file')
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Check if file exists and is accessible
|
|
8
|
-
* @param {String} filename
|
|
9
|
-
* @returns {Promise<Boolean>}
|
|
10
|
-
*/
|
|
11
|
-
module.exports.checkFile = filename =>
|
|
12
|
-
new Promise(resolve => {
|
|
13
|
-
debug('Trying to access `%s`', filename)
|
|
14
|
-
fs.access(filename, fs.constants.R_OK, error => {
|
|
15
|
-
if (error) {
|
|
16
|
-
debug('Unable to access file `%s` with error:', filename)
|
|
17
|
-
debug(error)
|
|
18
|
-
} else {
|
|
19
|
-
debug('Successfully accesses file `%s`', filename)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
resolve(!error)
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @param {String} filename
|
|
28
|
-
* @returns {Promise<Buffer|Null>}
|
|
29
|
-
*/
|
|
30
|
-
module.exports.readBufferFromFile = filename =>
|
|
31
|
-
new Promise(resolve => {
|
|
32
|
-
debug('Reading buffer from file `%s`', filename)
|
|
33
|
-
fs.readFile(filename, (error, file) => {
|
|
34
|
-
debug('Done reading buffer from file `%s`!', filename)
|
|
35
|
-
resolve(file)
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @param {String} filename
|
|
41
|
-
* @param {Buffer} buffer
|
|
42
|
-
* @returns {Promise<Void>}
|
|
43
|
-
*/
|
|
44
|
-
module.exports.writeBufferToFile = (filename, buffer) =>
|
|
45
|
-
new Promise(resolve => {
|
|
46
|
-
debug('Writing buffer to file `%s`', filename)
|
|
47
|
-
fs.writeFile(filename, buffer, () => {
|
|
48
|
-
debug('Done writing buffer to file `%s`!', filename)
|
|
49
|
-
resolve()
|
|
50
|
-
})
|
|
51
|
-
})
|
package/lib/gitWorkflow.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const debug = require('debug')('lint-staged:git')
|
|
4
|
-
const path = require('path')
|
|
5
|
-
|
|
6
|
-
const execGit = require('./execGit')
|
|
7
|
-
const { checkFile, readBufferFromFile, writeBufferToFile } = require('./file')
|
|
8
|
-
|
|
9
|
-
const MERGE_HEAD = 'MERGE_HEAD'
|
|
10
|
-
const MERGE_MODE = 'MERGE_MODE'
|
|
11
|
-
const MERGE_MSG = 'MERGE_MSG'
|
|
12
|
-
|
|
13
|
-
const STASH = 'lint-staged automatic backup'
|
|
14
|
-
|
|
15
|
-
const gitApplyArgs = ['apply', '-v', '--whitespace=nowarn', '--recount', '--unidiff-zero']
|
|
16
|
-
|
|
17
|
-
class GitWorkflow {
|
|
18
|
-
constructor(cwd) {
|
|
19
|
-
this.execGit = (args, options = {}) => execGit(args, { ...options, cwd })
|
|
20
|
-
this.unstagedDiff = null
|
|
21
|
-
this.cwd = cwd
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* These three files hold state about an ongoing git merge
|
|
25
|
-
* Resolve paths during constructor
|
|
26
|
-
*/
|
|
27
|
-
this.mergeHeadFile = path.resolve(this.cwd, '.git', MERGE_HEAD)
|
|
28
|
-
this.mergeModeFile = path.resolve(this.cwd, '.git', MERGE_MODE)
|
|
29
|
-
this.mergeMsgFile = path.resolve(this.cwd, '.git', MERGE_MSG)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get name of backup stash
|
|
34
|
-
*
|
|
35
|
-
* @param {Object} [options]
|
|
36
|
-
* @returns {Promise<Object>}
|
|
37
|
-
*/
|
|
38
|
-
async getBackupStash() {
|
|
39
|
-
const stashes = await this.execGit(['stash', 'list'])
|
|
40
|
-
const index = stashes.split('\n').findIndex(line => line.includes(STASH))
|
|
41
|
-
return `stash@{${index}}`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Create backup stashes, one of everything and one of only staged changes
|
|
46
|
-
* Staged files are left in the index for running tasks
|
|
47
|
-
*
|
|
48
|
-
* @param {Object} [options]
|
|
49
|
-
* @returns {Promise<void>}
|
|
50
|
-
*/
|
|
51
|
-
async stashBackup() {
|
|
52
|
-
debug('Backing up original state...')
|
|
53
|
-
|
|
54
|
-
// Git stash loses metadata about a possible merge mode
|
|
55
|
-
// Manually check and backup if necessary
|
|
56
|
-
if (await checkFile(this.mergeHeadFile)) {
|
|
57
|
-
debug('Detected current merge mode!')
|
|
58
|
-
debug('Backing up merge state...')
|
|
59
|
-
await Promise.all([
|
|
60
|
-
readBufferFromFile(this.mergeHeadFile).then(
|
|
61
|
-
mergeHead => (this.mergeHeadBuffer = mergeHead)
|
|
62
|
-
),
|
|
63
|
-
readBufferFromFile(this.mergeModeFile).then(
|
|
64
|
-
mergeMode => (this.mergeModeBuffer = mergeMode)
|
|
65
|
-
),
|
|
66
|
-
readBufferFromFile(this.mergeMsgFile).then(mergeMsg => (this.mergeMsgBuffer = mergeMsg))
|
|
67
|
-
])
|
|
68
|
-
debug('Done backing up merge state!')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Save stash of entire original state, including unstaged and untracked changes.
|
|
72
|
-
// This should remove all changes from the index.
|
|
73
|
-
// The `--keep-index` option cannot be used since it resurrects deleted files on
|
|
74
|
-
// git versions before v2.23.0 (https://github.com/git/git/blob/master/Documentation/RelNotes/2.23.0.txt#L322)
|
|
75
|
-
await this.execGit(['stash', 'save', '--quiet', '--include-untracked', STASH])
|
|
76
|
-
// Apply only the staged changes back to index
|
|
77
|
-
await this.execGit(['stash', 'apply', '--index', await this.getBackupStash()])
|
|
78
|
-
// Checkout everything just in case there are unstaged files left behind.
|
|
79
|
-
await this.execGit(['checkout', '.'])
|
|
80
|
-
// Since only staged files are now present, get a diff of unstaged changes
|
|
81
|
-
// by comparing current index against original stash, but in reverse
|
|
82
|
-
this.unstagedDiff = await this.execGit([
|
|
83
|
-
'diff',
|
|
84
|
-
'--unified=0',
|
|
85
|
-
'--no-color',
|
|
86
|
-
'--no-ext-diff',
|
|
87
|
-
'--patch',
|
|
88
|
-
await this.getBackupStash(),
|
|
89
|
-
'-R' // Show diff in reverse
|
|
90
|
-
])
|
|
91
|
-
debug('Done backing up original state!')
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Applies back task modifications, and unstaged changes hidden in the stash.
|
|
96
|
-
* In case of a merge-conflict retry with 3-way merge.
|
|
97
|
-
*
|
|
98
|
-
* @param {Object} [options]
|
|
99
|
-
* @returns {Promise<void>}
|
|
100
|
-
*/
|
|
101
|
-
async applyModifications() {
|
|
102
|
-
let modifiedFiles = await this.execGit(['ls-files', '--modified'])
|
|
103
|
-
if (modifiedFiles) {
|
|
104
|
-
modifiedFiles = modifiedFiles.split('\n')
|
|
105
|
-
debug('Detected files modified by tasks:')
|
|
106
|
-
debug(modifiedFiles)
|
|
107
|
-
debug('Adding files to index...')
|
|
108
|
-
await this.execGit(['add', modifiedFiles])
|
|
109
|
-
debug('Done adding files to index!')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (this.unstagedDiff) {
|
|
113
|
-
debug('Restoring unstaged changes...')
|
|
114
|
-
try {
|
|
115
|
-
await this.execGit(gitApplyArgs, { input: `${this.unstagedDiff}\n` })
|
|
116
|
-
} catch (error) {
|
|
117
|
-
debug('Error while restoring changes:')
|
|
118
|
-
debug(error)
|
|
119
|
-
debug('Retrying with 3-way merge')
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
// Retry with `--3way` if normal apply fails
|
|
123
|
-
await this.execGit([...gitApplyArgs, '--3way'], { input: `${this.unstagedDiff}\n` })
|
|
124
|
-
} catch (error2) {
|
|
125
|
-
debug('Error while restoring unstaged changes using 3-way merge:')
|
|
126
|
-
debug(error2)
|
|
127
|
-
throw new Error('Unstaged changes could not be restored due to a merge conflict!')
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
debug('Done restoring unstaged changes!')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Restore untracked files by reading from the third commit associated with the backup stash
|
|
134
|
-
// Git will return with error code if the commit doesn't exist
|
|
135
|
-
// See https://stackoverflow.com/a/52357762
|
|
136
|
-
try {
|
|
137
|
-
const backupStash = await this.getBackupStash()
|
|
138
|
-
const output = await this.execGit(['show', '--format=%b', `${backupStash}^3`])
|
|
139
|
-
const untrackedDiff = output.replace(/^\n*/, '') // remove empty lines from start of output
|
|
140
|
-
if (!untrackedDiff) return
|
|
141
|
-
await this.execGit([...gitApplyArgs], { input: `${untrackedDiff}\n` })
|
|
142
|
-
} catch (err) {} // eslint-disable-line no-empty
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Restore original HEAD state in case of errors
|
|
147
|
-
*
|
|
148
|
-
* @param {Object} [options]
|
|
149
|
-
* @returns {Promise<void>}
|
|
150
|
-
*/
|
|
151
|
-
async restoreOriginalState() {
|
|
152
|
-
debug('Restoring original state...')
|
|
153
|
-
const original = await this.getBackupStash()
|
|
154
|
-
await this.execGit(['reset', '--hard', 'HEAD'])
|
|
155
|
-
await this.execGit(['stash', 'apply', '--quiet', '--index', original])
|
|
156
|
-
debug('Done restoring original state!')
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Drop the created stashes after everything has run
|
|
161
|
-
*
|
|
162
|
-
* @param {Object} [options]
|
|
163
|
-
* @returns {Promise<void>}
|
|
164
|
-
*/
|
|
165
|
-
async dropBackup() {
|
|
166
|
-
debug('Dropping backup stash...')
|
|
167
|
-
const original = await this.getBackupStash()
|
|
168
|
-
await this.execGit(['stash', 'drop', '--quiet', original])
|
|
169
|
-
debug('Done dropping backup stash!')
|
|
170
|
-
|
|
171
|
-
if (this.mergeHeadBuffer) {
|
|
172
|
-
debug('Detected backup merge state!')
|
|
173
|
-
debug('Restoring merge state...')
|
|
174
|
-
await Promise.all([
|
|
175
|
-
writeBufferToFile(this.mergeHeadFile, this.mergeHeadBuffer),
|
|
176
|
-
writeBufferToFile(this.mergeModeFile, this.mergeModeBuffer),
|
|
177
|
-
writeBufferToFile(this.mergeMsgFile, this.mergeMsgBuffer)
|
|
178
|
-
])
|
|
179
|
-
debug('Done restoring merge state!')
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
module.exports = GitWorkflow
|
package/lib/runAll.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/** @typedef {import('./index').Logger} Logger */
|
|
4
|
-
|
|
5
|
-
const chalk = require('chalk')
|
|
6
|
-
const Listr = require('listr')
|
|
7
|
-
const symbols = require('log-symbols')
|
|
8
|
-
|
|
9
|
-
const generateTasks = require('./generateTasks')
|
|
10
|
-
const getStagedFiles = require('./getStagedFiles')
|
|
11
|
-
const GitWorkflow = require('./gitWorkflow')
|
|
12
|
-
const makeCmdTasks = require('./makeCmdTasks')
|
|
13
|
-
const resolveGitDir = require('./resolveGitDir')
|
|
14
|
-
|
|
15
|
-
const debugLog = require('debug')('lint-staged:run')
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* https://serverfault.com/questions/69430/what-is-the-maximum-length-of-a-command-line-in-mac-os-x
|
|
19
|
-
* https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation
|
|
20
|
-
* https://unix.stackexchange.com/a/120652
|
|
21
|
-
*/
|
|
22
|
-
const MAX_ARG_LENGTH =
|
|
23
|
-
(process.platform === 'darwin' && 262144) || (process.platform === 'win32' && 8191) || 131072
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Executes all tasks and either resolves or rejects the promise
|
|
27
|
-
*
|
|
28
|
-
* @param {object} options
|
|
29
|
-
* @param {Object} [options.config] - Task configuration
|
|
30
|
-
* @param {Object} [options.cwd] - Current working directory
|
|
31
|
-
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
|
|
32
|
-
* @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
|
|
33
|
-
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
|
|
34
|
-
* @param {boolean} [options.debug] - Enable debug mode
|
|
35
|
-
* @param {Logger} logger
|
|
36
|
-
* @returns {Promise}
|
|
37
|
-
*/
|
|
38
|
-
module.exports = async function runAll(
|
|
39
|
-
{ config, cwd = process.cwd(), debug = false, quiet = false, relative = false, shell = false },
|
|
40
|
-
logger = console
|
|
41
|
-
) {
|
|
42
|
-
debugLog('Running all linter scripts')
|
|
43
|
-
|
|
44
|
-
const gitDir = await resolveGitDir({ cwd })
|
|
45
|
-
|
|
46
|
-
if (!gitDir) {
|
|
47
|
-
throw new Error('Current directory is not a git directory!')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
debugLog('Resolved git directory to be `%s`', gitDir)
|
|
51
|
-
|
|
52
|
-
const files = await getStagedFiles({ cwd: gitDir })
|
|
53
|
-
|
|
54
|
-
if (!files) {
|
|
55
|
-
throw new Error('Unable to get staged files!')
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
debugLog('Loaded list of staged files in git:\n%O', files)
|
|
59
|
-
|
|
60
|
-
const argLength = files.join(' ').length
|
|
61
|
-
if (argLength > MAX_ARG_LENGTH) {
|
|
62
|
-
logger.warn(`
|
|
63
|
-
${symbols.warning} ${chalk.yellow(
|
|
64
|
-
`lint-staged generated an argument string of ${argLength} characters, and commands might not run correctly on your platform.
|
|
65
|
-
It is recommended to use functions as linters and split your command based on the number of staged files. For more info, please visit:
|
|
66
|
-
https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-commands`
|
|
67
|
-
)}
|
|
68
|
-
`)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const tasks = generateTasks({ config, cwd, gitDir, files, relative })
|
|
72
|
-
|
|
73
|
-
// lint-staged 10 will automatically add modifications to index
|
|
74
|
-
// Warn user when their command includes `git add`
|
|
75
|
-
let hasDeprecatedGitAdd = false
|
|
76
|
-
|
|
77
|
-
const listrTasks = tasks.map(task => {
|
|
78
|
-
const subTasks = makeCmdTasks({ commands: task.commands, files: task.fileList, gitDir, shell })
|
|
79
|
-
|
|
80
|
-
if (subTasks.some(subTask => subTask.command.includes('git add'))) {
|
|
81
|
-
hasDeprecatedGitAdd = true
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
title: `Running tasks for ${task.pattern}`,
|
|
86
|
-
task: async () =>
|
|
87
|
-
new Listr(subTasks, {
|
|
88
|
-
// In sub-tasks we don't want to run concurrently
|
|
89
|
-
// and we want to abort on errors
|
|
90
|
-
dateFormat: false,
|
|
91
|
-
concurrent: false,
|
|
92
|
-
exitOnError: true
|
|
93
|
-
}),
|
|
94
|
-
skip: () => {
|
|
95
|
-
if (task.fileList.length === 0) {
|
|
96
|
-
return `No staged files match ${task.pattern}`
|
|
97
|
-
}
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
if (hasDeprecatedGitAdd) {
|
|
104
|
-
logger.warn(`${symbols.warning} ${chalk.yellow(
|
|
105
|
-
`Some of your tasks use \`git add\` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.`
|
|
106
|
-
)}
|
|
107
|
-
`)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// If all of the configured tasks should be skipped
|
|
111
|
-
// avoid executing any lint-staged logic
|
|
112
|
-
if (listrTasks.every(task => task.skip())) {
|
|
113
|
-
logger.log('No staged files match any of provided globs.')
|
|
114
|
-
return 'No tasks to run.'
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const listrOptions = {
|
|
118
|
-
dateFormat: false,
|
|
119
|
-
renderer: (quiet && 'silent') || (debug && 'verbose') || 'update'
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const git = new GitWorkflow(gitDir)
|
|
123
|
-
|
|
124
|
-
const runner = new Listr(
|
|
125
|
-
[
|
|
126
|
-
{
|
|
127
|
-
title: 'Preparing...',
|
|
128
|
-
task: () => git.stashBackup()
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
title: 'Running tasks...',
|
|
132
|
-
task: () => new Listr(listrTasks, { ...listrOptions, concurrent: true, exitOnError: false })
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
title: 'Applying modifications...',
|
|
136
|
-
skip: ctx => ctx.hasErrors && 'Skipped because of errors from tasks',
|
|
137
|
-
task: () => git.applyModifications()
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
title: 'Reverting to original state...',
|
|
141
|
-
enabled: ctx => ctx.hasErrors,
|
|
142
|
-
task: () => git.restoreOriginalState()
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
title: 'Cleaning up...',
|
|
146
|
-
task: () => git.dropBackup()
|
|
147
|
-
}
|
|
148
|
-
],
|
|
149
|
-
listrOptions
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
await runner.run()
|
|
154
|
-
} catch (error) {
|
|
155
|
-
if (error.message.includes('Another git process seems to be running in this repository')) {
|
|
156
|
-
logger.error(`
|
|
157
|
-
${symbols.error} ${chalk.red(`lint-staged failed due to a git error.
|
|
158
|
-
Any lost modifications can be restored from a git stash:
|
|
159
|
-
|
|
160
|
-
> git stash list
|
|
161
|
-
stash@{0}: On master: automatic lint-staged backup
|
|
162
|
-
> git stash pop stash@{0}`)}
|
|
163
|
-
`)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
throw error
|
|
167
|
-
}
|
|
168
|
-
}
|